diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..b884aa52a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +Contributions to the library are welcome! Here are some guidelines to follow. + +## Suggestions + +Please make suggestions for additional components or enhancements to the +codebase by opening an [issue](https://github.com/gpiozero/gpiozero/issues) +explaining your reasoning clearly. + +## Bugs + +Please submit bug reports by opening an [issue](https://github.com/gpiozero/gpiozero/issues) +explaining the problem clearly using code examples. + +## Documentation + +The documentation source lives in the [docs](https://github.com/gpiozero/gpiozero/tree/master/docs) +folder. Contributions to the documentation are welcome but should be easy to +read and understand. + +## Commit messages and pull requests + +Commit messages should be concise but descriptive, and in the form of a patch +description, i.e. instructional not past tense ("Add LED example" not "Added +LED example"). + +Commits which close (or intend to close) an issue should include the phrase +"fix #123" or "close #123" where `#123` is the issue number, as well as +include a short description, for example: "Add LED example, close #123", and +pull requests should aim to match or closely match the corresponding issue +title. + +## Backwards compatibility + +Since this library reached v1.0 we aim to maintain backwards-compatibility +thereafter. Changes which break backwards-compatibility will not be accepted. + +## Python 2/3 + +The library is 100% compatible with both Python 2 and 3. We intend to drop +Python 2 support in 2020 when Python 2 reaches [end-of-life](http://legacy.python.org/dev/peps/pep-0373/). diff --git a/.github/ISSUE_TEMPLATE/api-change.md b/.github/ISSUE_TEMPLATE/api-change.md new file mode 100644 index 000000000..f68fae34e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/api-change.md @@ -0,0 +1,17 @@ +--- +name: API change +about: Suggest an API change +--- + +Short description of change: e.g. Add color property to LED class +Where would this change be made: e.g. LED class + +Purpose of change: + + + +Please provide example usage of gpiozero with your suggested change: + +```python +print("code here") +``` diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 000000000..798a4b93f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,18 @@ +--- +name: Bug report +about: Report a bug in the gpiozero library +--- + +Operating system: e.g. Raspbian Buster +Python version: e.g. 3.7 +Pi model: e.g. Pi 4 Model B +GPIO Zero version: e.g. 1.5.1 +Pin factory used: e.g. RPiGPIO +See http://rpf.io/gpzissue for information on how to find out these details + +Please give us as much information about your issue as possible. Write your +code inside code blocks like so: + +```python +print("code here") +``` diff --git a/.github/ISSUE_TEMPLATE/new-device.md b/.github/ISSUE_TEMPLATE/new-device.md new file mode 100644 index 000000000..750630a44 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-device.md @@ -0,0 +1,15 @@ +--- +name: New device +about: Request a new device to be added to the gpiozero library +--- + +Device name: e.g. Temperature sensor +Protocol: e.g. GPIO/SPI/I2C +URL of example product: +Further information e.g. data sheet: + +Please provide example code for using this device with Python, using another library: + +```python +print("code here") +``` diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..998ed68d8 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,52 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + python: "3.9" + experimental: false + - os: ubuntu-22.04 + python: "3.10" + experimental: false + - os: ubuntu-22.04 + python: "3.11" + experimental: false + - os: ubuntu-22.04 + python: "3.12" + experimental: false + - os: ubuntu-22.04 + python: "3.13" + experimental: false + + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + + steps: + - name: Install Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Checkout GPIO Zero + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + make develop + + - name: Run tests + run: | + pytest -v -k 'not test_real_pins.py' diff --git a/.gitignore b/.gitignore index 37728852f..87e90cf75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Python stuff *.py[cdo] # Editor detritus @@ -8,21 +9,15 @@ tags # Packaging detritus *.egg *.egg-info +*.pyc +*.whl dist build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg - -# Installer logs -pip-log.txt # Unit test / coverage reports coverage -.coverage +.coverage* .tox - +.cache +.pytest_cache +.mypy_cache diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..91ac3ec43 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: rtd_requirements.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index c92d92389..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,22 +0,0 @@ -# Contributing - -This module was designed for use in education; particularly for young children. It is not intended to replace `RPi.GPIO` and it does not claim to be suitable for all purposes. It is intended to provide a simple interface to everyday components. - -If a proposed change added an advanced feature but made basic usage more complex, it is unlikely to be added. - -## Suggestions - -Please make suggestions by opening an [issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining your reasoning clearly. - -## Bugs - -Please submit bug reports by opening an [issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining the problem clearly using code examples. - -## Documentation - -The documentation source lives in the [docs](https://github.com/RPi-Distro/python-gpiozero/tree/master/docs) folder and is rendered from markdown into HTML using [mkdocs](http://www.mkdocs.org/). Contributions to the documentation are welcome but should be easy to read and understand. - -## Python - -- Python 2/3 compatibility -- PEP8-compliance (with exceptions) diff --git a/LICENCE.txt b/LICENSE.rst similarity index 59% rename from LICENCE.txt rename to LICENSE.rst index cdf573d15..37188b72e 100644 --- a/LICENCE.txt +++ b/LICENSE.rst @@ -1,18 +1,18 @@ -Copyright 2015- Raspberry Pi Foundation +SPDX-License-Identifier: BSD-3-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. +* Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 9561fb106..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.rst diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..ae3d32631 --- /dev/null +++ b/Makefile @@ -0,0 +1,137 @@ +# vim: set noet sw=4 ts=4 fileencoding=utf-8: + +# External utilities +PYTHON ?= python3 +PIP ?= pip +PYTEST ?= pytest +TWINE ?= twine +PYFLAGS ?= +DEST_DIR ?= / +DOC_HTML=docs/build/html +DOC_REQS=rtd_requirements.txt + +# Calculate the base names of the distribution, the location of all source, +# documentation, packaging, icon, and executable script files +NAME:=$(shell $(PYTHON) $(PYFLAGS) setup.py --name) +WHEEL_NAME:=$(subst -,_,$(NAME)) +VER:=$(shell $(PYTHON) $(PYFLAGS) setup.py --version) +PY_SOURCES:=$(shell \ + $(PYTHON) $(PYFLAGS) setup.py egg_info >/dev/null 2>&1 && \ + cat $(WHEEL_NAME).egg-info/SOURCES.txt | grep -v "\.egg-info" | grep -v "\.mo$$") +DOC_SOURCES:=docs/conf.py \ + $(wildcard docs/*.png) \ + $(wildcard docs/*.svg) \ + $(wildcard docs/*.dot) \ + $(wildcard docs/*.mscgen) \ + $(wildcard docs/*.gpi) \ + $(wildcard docs/*.rst) \ + $(wildcard docs/*.pdf) +SUBDIRS:= + +# Calculate the name of all outputs +DIST_WHEEL=dist/$(WHEEL_NAME)-$(VER)-py3-none-any.whl +DIST_TAR=dist/$(NAME)-$(VER).tar.gz +DIST_ZIP=dist/$(NAME)-$(VER).zip +MAN_PAGES=man/pinout.1 man/pintest.1 man/remote-gpio.7 man/gpiozero-env.7 + + +# Default target +all: + @echo "make install - Install on local system" + @echo "make develop - Install symlinks for development" + @echo "make test - Run tests" + @echo "make doc - Generate HTML and PDF documentation" + @echo "make doc-serve - Serve the docs locally" + @echo "make doc-reqs - Generate requirements file for RTD" + @echo "make source - Create source package" + @echo "make wheel - Generate a PyPI wheel package" + @echo "make zip - Generate a source zip package" + @echo "make tar - Generate a source tar package" + @echo "make dist - Generate all packages" + @echo "make clean - Get rid of all generated files" + @echo "make release - Create and tag a new release" + @echo "make upload - Upload the new release to repositories" + +install: $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py install --root $(DEST_DIR) + +doc: $(DOC_SOURCES) + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(MAKE) -C docs epub + $(MAKE) -C docs latexpdf + $(MAKE) $(MAN_PAGES) + +doc-serve: + python -m http.server -d $(DOC_HTML) + +doc-reqs: + echo "." > $(DOC_REQS) + pip freeze | grep -i sphinx >> $(DOC_REQS) + +preview: + $(MAKE) -C docs preview + +source: $(DIST_TAR) $(DIST_ZIP) + +wheel: $(DIST_WHEEL) + +zip: $(DIST_ZIP) + +tar: $(DIST_TAR) + +dist: $(DIST_WHEEL) $(DIST_TAR) $(DIST_ZIP) + +develop: + @# These have to be done separately to avoid a cockup... + $(PIP) install -U setuptools + $(PIP) install -U pip + $(PIP) install -U twine + $(PIP) install -U tox + $(PIP) install -e .[doc,test] + +test: + $(PYTEST) + +clean: + rm -fr dist/ build/ man/ .pytest_cache/ .mypy_cache/ $(WHEEL_NAME).egg-info/ tags .coverage* + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir clean; \ + done + find $(CURDIR) -name "*.pyc" -delete + find $(CURDIR) -name "__pycache__" -delete + +tags: $(PY_SOURCES) + ctags -R --languages="Python" $(PY_SOURCES) + +lint: $(PY_SOURCES) + pylint $(WHEEL_NAME) + +$(SUBDIRS): + $(MAKE) -C $@ + +$(MAN_PAGES): $(DOC_SOURCES) + $(MAKE) -C docs man + mkdir -p man/ + cp build/man/*.[0-9] man/ + +$(DIST_TAR): $(PY_SOURCES) $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py sdist --formats gztar + +$(DIST_ZIP): $(PY_SOURCES) $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py sdist --formats zip + +$(DIST_WHEEL): $(PY_SOURCES) $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py bdist_wheel + +release: + $(MAKE) clean + test -z "$(shell git status --porcelain)" + git tag -s v$(VER) -m "Release v$(VER)" + git push origin v$(VER) + +upload: $(DIST_TAR) $(DIST_WHEEL) + $(TWINE) check $(DIST_TAR) $(DIST_WHEEL) + $(TWINE) upload $(DIST_TAR) $(DIST_WHEEL) + +.PHONY: all install develop test doc doc-serve doc-reqs source wheel zip tar dist clean tags release upload $(SUBDIRS) diff --git a/README.rst b/README.rst index 17e1bf74c..52b6dec8e 100644 --- a/README.rst +++ b/README.rst @@ -2,41 +2,25 @@ gpiozero ======== -A simple interface to everyday GPIO components used with Raspberry Pi +A simple interface to GPIO devices with `Raspberry Pi`_, developed and +maintained by `Ben Nuttall`_ and `Dave Jones`_. -*A work in progress* +.. _Raspberry Pi: https://www.raspberrypi.com/ +.. _Ben Nuttall: https://github.com/bennuttall +.. _Dave Jones: https://github.com/waveform80 -Motivation -========== - -The "hello world" program in Java is at least 5 lines long, and contains 11 -jargon words which students are taught to ignore. - -The "hello world" program in Python is one simple line. However, the "hello -world" of physical computing in Python (flashing an LED) is similar to the Java -program: 6 lines of code to flash an LED. - -Young children and beginners shouldn't need to sit and copy out several lines -of text they're told to ignore. They should be able to read their code and -understand what it means. - -Install -======= - -Install with pip:: - - sudo pip install gpiozero - sudo pip-3.2 install gpiozero - -Usage +About ===== -Example usage for flashing an LED:: +Component interfaces are provided to allow a frictionless way to get started +with physical computing: + +.. code:: python from gpiozero import LED from time import sleep - led = LED(2) + led = LED(17) while True: led.on() @@ -44,17 +28,196 @@ Example usage for flashing an LED:: led.off() sleep(1) -Development -=========== +With very little code, you can quickly get going connecting your components +together: + +.. code:: python + + from gpiozero import LED, Button + from signal import pause + + led = LED(17) + button = Button(3) + + button.when_pressed = led.on + button.when_released = led.off + + pause() -This project is being developed on `GitHub`_. Join in: +You can advance to using the declarative paradigm along with provided +to describe the behaviour of devices and their interactions: -* Provide suggestions -* Help design the `API`_ -* Contribute to the code +.. code:: python -Alternatively, email suggestions and feedback to ben@raspberrypi.org + from gpiozero import OutputDevice, MotionSensor, LightSensor + from gpiozero.tools import booleanized, all_values + from signal import pause + garden = OutputDevice(17) + motion = MotionSensor(4) + light = LightSensor(5) -.. _`GitHub`: https://github.com/RPi-Distro/python-gpiozero -.. _`API`: https://github.com/RPi-Distro/python-gpiozero/issues/7 + garden.source = all_values(booleanized(light, 0, 0.1), motion) + + pause() + +See the chapter on `Source/Values`_ for more information. + +.. _Source/Values: https://gpiozero.readthedocs.io/en/stable/source_values.html + +The library includes interfaces to many simple everyday components, as well as +some more complex things like sensors, analogue-to-digital converters, full +colour LEDs, robotics kits and more. See the `Recipes`_ chapter of the +documentation for ideas on how to get started. + +.. _Recipes: https://gpiozero.readthedocs.io/en/stable/recipes.html + +Pin factories +============= + +GPIO Zero builds on a number of underlying pin libraries, including `RPi.GPIO`_ +and `pigpio`_, each with their own benefits. You can select a particular pin +library to be used, either for the whole script or per-device, according to your +needs. See the section on `changing the pin factory`_. + +.. _RPi.GPIO: https://pypi.org/project/RPi.GPIO +.. _pigpio: https://pypi.org/project/pigpio +.. _changing the pin factory: https://gpiozero.readthedocs.io/en/stable/api_pins.html#changing-the-pin-factory + +A "mock pin" interface is also provided for testing purposes. Read more about +this in the section on `mock pins`_. + +.. _mock pins: https://gpiozero.readthedocs.io/en/stable/api_pins.html#mock-pins + +Installation +============ + +GPIO Zero is installed by default in the Raspberry Pi OS desktop image, +available from `raspberrypi.com`_. To install on Raspberry Pi OS Lite or other +operating systems, including for PCs using remote GPIO, see the `Installing`_ +chapter. + +.. _raspberrypi.com: https://www.raspberrypi.com/software/ +.. _Installing: https://gpiozero.readthedocs.io/en/stable/installing.html + +Documentation +============= + +Comprehensive documentation is available at https://gpiozero.readthedocs.io/. +Please refer to the `Contributing`_ and `Development`_ chapters in the +documentation for information on contributing to the project. + +.. _Contributing: https://gpiozero.readthedocs.io/en/stable/contributing.html +.. _Development: https://gpiozero.readthedocs.io/en/stable/development.html + +Issues and questions +==================== + +If you have a feature request or bug report, please open an `issue on GitHub`_. +If you have a question or need help, this may be better suited to our `GitHub +discussion board`_, the `Raspberry Pi Stack Exchange`_ or the `Raspberry Pi +Forums`_. + +.. _issue on GitHub: https://github.com/gpiozero/gpiozero/issues/new +.. _GitHub discussion board: https://github.com/gpiozero/gpiozero/discussions +.. _Raspberry Pi Stack Exchange: https://raspberrypi.stackexchange.com/ +.. _Raspberry Pi Forums: https://forums.raspberrypi.com/ + +Contributors +============ + +- `Alex Chan`_ +- `Alex Eames`_ +- `Andrew Scheller`_ +- `Barry Byford`_ +- `Cameron Davidson-Pilon`_ +- `Carl Monk`_ +- `Claire Pollard`_ +- `Clare Macrae`_ +- `Dan Jackson`_ +- `Daniele Procida`_ +- `damosurfer`_ +- `David Glaude`_ +- `Delcio Torres`_ +- `Edward Betts`_ +- `Fatih Sarhan`_ +- `Fangchen Li`_ +- `G.S.`_ +- `gnicki`_ +- `Ian Harcombe`_ +- `Jack Wearden`_ +- `Jeevan M R`_ +- `Josh Thorpe`_ +- `Kyle Morgan`_ +- `Linus Groh`_ +- `Mahallon`_ +- `Maksim Levental`_ +- `Martchus`_ +- `Martin O'Hanlon`_ +- `Mike Kazantsev`_ +- `Paulo Mateus`_ +- `Phil Howard`_ +- `Philippe Muller`_ +- `Rick Ansell`_ +- `Rimas Misevičius`_ +- `Robert Erdin`_ +- `Russel Winder`_ +- `Ryan Walmsley`_ +- `Schelto van Doorn`_ +- `Sofiia Kosovan`_ +- `Steve Amor`_ +- `Stewart Adcock`_ +- `Thijs Triemstra`_ +- `Tim Golden`_ +- `Yisrael Dov Lebow`_ + +See the `contributors page`_ on GitHub for more info. + +.. _Alex Chan: https://github.com/gpiozero/gpiozero/commits?author=alexwlchan +.. _Alex Eames: https://github.com/gpiozero/gpiozero/commits?author=raspitv +.. _Andrew Scheller: https://github.com/gpiozero/gpiozero/commits?author=lurch +.. _Barry Byford: https://github.com/gpiozero/gpiozero/commits?author=ukBaz +.. _Cameron Davidson-Pilon: https://github.com/gpiozero/gpiozero/commits?author=CamDavidsonPilon +.. _Carl Monk: https://github.com/gpiozero/gpiozero/commits?author=ForToffee +.. _Chris R: https://github.com/gpiozero/gpiozero/commits?author=chrisruk +.. _Claire Pollard: https://github.com/gpiozero/gpiozero/commits?author=tuftii +.. _Clare Macrae: https://github.com/gpiozero/gpiozero/commits?author=claremacrae +.. _Dan Jackson: https://github.com/gpiozero/gpiozero/commits?author=e28eta +.. _Daniele Procida: https://github.com/evildmp +.. _Dariusz Kowalczyk: https://github.com/gpiozero/gpiozero/commits?author=darton +.. _damosurfer: https://github.com/gpiozero/gpiozero/commits?author=damosurfer +.. _David Glaude: https://github.com/gpiozero/gpiozero/commits?author=dglaude +.. _Delcio Torres: https://github.com/gpiozero/gpiozero/commits?author=delciotorres +.. _Edward Betts: https://github.com/gpiozero/gpiozero/commits?author=edwardbetts +.. _Fatih Sarhan: https://github.com/gpiozero/gpiozero/commits?author=f9n +.. _Fangchen Li: https://github.com/gpiozero/gpiozero/commits?author=fangchenli +.. _G.S.: https://github.com/gpiozero/gpiozero/commits?author=gszy +.. _gnicki: https://github.com/gpiozero/gpiozero/commits?author=gnicki2000 +.. _Ian Harcombe: https://github.com/gpiozero/gpiozero/commits?author=MrHarcombe +.. _Jack Wearden: https://github.com/gpiozero/gpiozero/commits?author=NotBobTheBuilder +.. _Jeevan M R: https://github.com/gpiozero/gpiozero/commits?author=jee1mr +.. _Josh Thorpe: https://github.com/gpiozero/gpiozero/commits?author=ThorpeJosh +.. _Kyle Morgan: https://github.com/gpiozero/gpiozero/commits?author=knmorgan +.. _Linus Groh: https://github.com/gpiozero/gpiozero/commits?author=linusg +.. _Mahallon: https://github.com/gpiozero/gpiozero/commits?author=Mahallon +.. _Maksim Levental: https://github.com/gpiozero/gpiozero/commits?author=makslevental +.. _Martchus: https://github.com/gpiozero/gpiozero/commits?author=Martchus +.. _Martin O'Hanlon: https://github.com/martinohanlon/commits?author=martinohanlon +.. _Mike Kazantsev: https://github.com/gpiozero/gpiozero/commits?author=mk-fg +.. _Paulo Mateus: https://github.com/gpiozero/gpiozero/commits?author=SrMouraSilva +.. _Phil Howard: https://github.com/gpiozero/gpiozero/commits?author=Gadgetoid +.. _Philippe Muller: https://github.com/gpiozero/gpiozero/commits?author=pmuller +.. _Rick Ansell: https://github.com/gpiozero/gpiozero/commits?author=ricksbt +.. _Rimas Misevičius: https://github.com/gpiozero/gpiozero/commits?author=rmisev +.. _Robert Erdin: https://github.com/gpiozero/gpiozero/commits?author=roberterdin +.. _Russel Winder: https://github.com/russel +.. _Ryan Walmsley: https://github.com/gpiozero/gpiozero/commits?author=ryanteck +.. _Schelto van Doorn: https://github.com/gpiozero/gpiozero/commits?author=goloplo +.. _Sofiia Kosovan: https://github.com/gpiozero/gpiozero/commits?author=SofiiaKosovan +.. _Steve Amor: https://github.com/gpiozero/gpiozero/commits?author=SteveAmor +.. _Stewart Adcock: https://github.com/gpiozero/gpiozero/commits?author=stewartadcock +.. _Thijs Triemstra: https://github.com/gpiozero/gpiozero/commits?author=thijstriemstra +.. _Tim Golden: https://github.com/gpiozero/gpiozero/commits?author=tjguk +.. _Yisrael Dov Lebow: https://github.com/gpiozero/gpiozero/commits?author=yisraeldov + +.. _contributors page: https://github.com/gpiozero/gpiozero/graphs/contributors diff --git a/RELEASE.rst b/RELEASE.rst new file mode 100644 index 000000000..1f24c7dd9 --- /dev/null +++ b/RELEASE.rst @@ -0,0 +1,51 @@ +================= +Release Procedure +================= + +On your build Pi, perform the following steps: + +1. Ensure you have a reliable Internet connection (preferably Ethernet). + +2. Ensure you have the following Debian packages installed: ``devscripts, dput, + python-all, python3-all, gnupg, build-essential, git, python-setuptools, + python3-setuptools, python-rpi.gpio, python3-rpi.gpio`` + +3. Ensure you have a valid ``~/.pypirc`` configuration. For example:: + + [distutils] + index-servers = + pypi + + [pypi] + username:my_username + password:my_long_password + +4. Ensure you have a valid ``~/.dput.cf`` setup which includes the + ``[raspberrypi]`` target. For example:: + + [raspberrypi] + fqdn = build-master.raspberrypi.org + incoming = incoming + login = incoming + method = scp + +5. Ensure you have a valid public/private key-pair defined for GNUPG. + +6. In the root ``gpiozero`` directory, run ``make release``. This will launch + ``dch`` to update the Debian changelog. Fill this out properly (ticket + references!) and the release will be generated, tagged, signed, and + registered with GitHub and PyPI. + + .. note:: + + Although the release has been registered at this point, no packages + have been generated or uploaded to any service. + +7. Still in the root ``gpiozero`` directory, run ``make upload``. This will + generate the actual debian packages, upload them to Raspbian, and upload + the source package to PyPI. + +8. On GitHub, close any milestone associated with the release. + +9. On ReadTheDocs, update the project configuration to build the new release, + then set it to the default version for the project. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..45ea56710 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,188 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = ../build +PY_SOURCES := $(wildcard ../gpiozero/*.py ../gpiozero/pins/*.py) +DOT_DIAGRAMS := $(wildcard images/*.dot images/*/*.dot) +MSC_DIAGRAMS := $(wildcard images/*.mscgen images/*/*.mscgen) +GPI_DIAGRAMS := $(wildcard images/*.gpi images/*/*.gpi) +SVG_IMAGES := $(wildcard images/*.svg images/*/*.svg) $(DOT_DIAGRAMS:%.dot=%.svg) $(MSC_DIAGRAMS:%.mscgen=%.svg) +PNG_IMAGES := $(wildcard images/*.png images/*/*.png) $(GPI_DIAGRAMS:%.gpi=%.png) $(SVG_IMAGES:%.svg=%.png) +PDF_IMAGES := $(SVG_IMAGES:%.svg=%.pdf) $(GPI_DIAGRAMS:%.gpi=%.pdf) $(DOT_DIAGRAMS:%.dot=%.pdf) $(MSC_DIAGRAMS:%.mscgen=%.pdf) +INKSCAPE_VER := $(shell inkscape --version | sed -ne '/^Inkscape/ s/^Inkscape \([0-9]\+\)\..*$$/\1/p') + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " preview to start a web-server that watches for file changes" + @echo " and re-builds the docs when required" + @echo " json to make JSON files" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: $(SVG_IMAGES) $(PNG_IMAGES) + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: $(PDF_IMAGES) + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: $(PDF_IMAGES) + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +preview: + ../scripts/previewer $(BUILDDIR)/html + +images/device_hierarchy.dot: $(PY_SOURCES) + ../scripts/class_graph \ + -i Device \ + -i SharedMixin \ + -x SPI -x SPISoftwareBus > $@ + +images/composite_device_hierarchy.dot: $(PY_SOURCES) + ../scripts/class_graph \ + -i CompositeDevice \ + -i Energenie \ + -i LedBord \ + -x Motor \ + -x PhaseEnableMotor \ + -x Servo \ + -o SourceMixin > $@ + +images/spi_device_hierarchy.dot: $(PY_SOURCES) + ../scripts/class_graph -i SPIDevice > $@ + +images/output_device_hierarchy.dot: $(PY_SOURCES) + ../scripts/class_graph \ + -i OutputDevice \ + -i RGBLED \ + -i Servo \ + -i Motor \ + -i PhaseEnableMotor \ + -i TonalBuzzer \ + -x LedBorg \ + -x SPI -o SourceMixin > $@ + +images/input_device_hierarchy.dot: $(PY_SOURCES) + ../scripts/class_graph -i InputDevice -o EventsMixin -o HoldMixin > $@ + +images/internal_device_hierarchy.dot: $(PY_SOURCES) + ../scripts/class_graph -i InternalDevice -o EventsMixin > $@ + +%.svg: %.mscgen + mscgen -T svg -o $@ $< + +%.svg: %.dot + dot -T svg -o $@ $< + +%.png: %.gpi + gnuplot -e "set term pngcairo transparent size 400,400" $< > $@ + +ifeq ($(INKSCAPE_VER),0) +%.png: %.svg + inkscape --export-dpi 150 -e $@ $< + +%.pdf: %.svg + inkscape -A $@ $< +else +%.png: %.svg + inkscape --export-dpi 150 --export-type png -o $@ $< + +%.pdf: %.svg + inkscape --export-type pdf -o $@ $< +endif + +%.pdf: %.gpi + gnuplot -e "set term pdfcairo size 5cm,5cm" $< > $@ + +%.pdf: %.mscgen + mscgen -T eps -o - $< | ps2pdf -dEPSCrop - $@ + +.PHONY: help clean html preview json epub latex latexpdf text man changes linkcheck doctest gettext diff --git a/docs/_static/keep-me b/docs/_static/keep-me new file mode 100644 index 000000000..c5bef2af0 --- /dev/null +++ b/docs/_static/keep-me @@ -0,0 +1 @@ +This file only exists for git to notice the directory diff --git a/docs/api_boards.rst b/docs/api_boards.rst new file mode 100644 index 000000000..74a53dd12 --- /dev/null +++ b/docs/api_boards.rst @@ -0,0 +1,276 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2021 Dave Jones +.. Copyright (c) 2016-2020 Ben Nuttall +.. Copyright (c) 2018 Claire Pollard +.. Copyright (c) 2016 Andrew Scheller +.. Copyright (c) 2016 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +============================ +API - Boards and Accessories +============================ + +.. module:: gpiozero.boards + +.. currentmodule:: gpiozero + +These additional interfaces are provided to group collections of components +together for ease of use, and as examples. They are composites made up of +components from the various :doc:`api_input` and :doc:`api_output` provided by +GPIO Zero. See those pages for more information on using components +individually. + +.. note:: + + All GPIO pin numbers use Broadcom (BCM) numbering by default. See the + :ref:`pin-numbering` section for more information. + + +Regular Classes +=============== + +The following classes are intended for general use with the devices they are +named after. All classes in this section are concrete (not abstract). + + +LEDBoard +-------- + +.. autoclass:: LEDBoard + :members: on, off, blink, pulse, toggle + + +LEDBarGraph +----------- + +.. autoclass:: LEDBarGraph + :members: value, source, values, lit_count + + +LEDCharDisplay +-------------- + +.. autoclass:: LEDCharDisplay + :members: value, font + + +LEDMultiCharDisplay +------------------- + +.. autoclass:: LEDMultiCharDisplay + :members: value, plex_delay + + +LEDCharFont +----------- + +.. autoclass:: LEDCharFont + + +ButtonBoard +----------- + +.. autoclass:: ButtonBoard + :members: wait_for_press, wait_for_release, is_pressed, pressed_time, when_pressed, when_released, value + + +TrafficLights +------------- + +.. autoclass:: TrafficLights + :members: + + +TrafficLightsBuzzer +------------------- + +.. autoclass:: TrafficLightsBuzzer + :members: + + +PiHutXmasTree +------------- + +.. autoclass:: PiHutXmasTree + :members: + + +LedBorg +------- + +.. autoclass:: LedBorg + :members: + + +PiLiter +------- + +.. autoclass:: PiLiter + :members: + + +PiLiterBarGraph +--------------- + +.. autoclass:: PiLiterBarGraph + :members: + + +PiTraffic +--------- + +.. autoclass:: PiTraffic + :members: + + +PiStop +------ + +.. autoclass:: PiStop + :members: + + +FishDish +-------- + +.. autoclass:: FishDish + :members: + + +TrafficHat +---------- + +.. autoclass:: TrafficHat + :members: + + +TrafficpHat +----------- + +.. autoclass:: TrafficpHat + :members: + + +JamHat +------ + +.. autoclass:: JamHat + :members: + + +Pibrella +-------- + +.. autoclass:: Pibrella + :members: + + +Robot +----- + +.. autoclass:: Robot + :members: + + +PhaseEnableRobot +---------------- + +.. autoclass:: PhaseEnableRobot + :members: + + +RyanteckRobot +------------- + +.. autoclass:: RyanteckRobot + :members: + + +CamJamKitRobot +-------------- + +.. autoclass:: CamJamKitRobot + :members: + + +PololuDRV8835Robot +------------------ + +.. autoclass:: PololuDRV8835Robot + :members: + + +Energenie +--------- + +.. autoclass:: Energenie + :members: on, off, socket, value + + +StatusZero +---------- + +.. autoclass:: StatusZero + :members: + + +StatusBoard +----------- + +.. autoclass:: StatusBoard + :members: + + +SnowPi +------ + +.. autoclass:: SnowPi + :members: + + +PumpkinPi +--------- + +.. autoclass:: PumpkinPi + :members: + + +Base Classes +============ + +The classes in the sections above are derived from a series of base classes, +some of which are effectively abstract. The classes form the (partial) +hierarchy displayed in the graph below: + +.. image:: images/composite_device_hierarchy.* + +For composite devices, the following chart shows which devices are composed of +which other devices: + +.. image:: images/composed_devices.* + +The following sections document these base classes for advanced users that wish +to construct classes for their own devices. + + +LEDCollection +------------- + +.. autoclass:: LEDCollection + :members: + + +CompositeOutputDevice +--------------------- + +.. autoclass:: CompositeOutputDevice + :members: + + +CompositeDevice +--------------- + +.. autoclass:: CompositeDevice + :members: diff --git a/docs/api_exc.rst b/docs/api_exc.rst new file mode 100644 index 000000000..6813f3469 --- /dev/null +++ b/docs/api_exc.rst @@ -0,0 +1,218 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2016-2023 Dave Jones +.. Copyright (c) 2019 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +================ +API - Exceptions +================ + +.. module:: gpiozero.exc + +.. currentmodule:: gpiozero + +The following exceptions are defined by GPIO Zero. Please note that multiple +inheritance is heavily used in the exception hierarchy to make testing for +exceptions easier. For example, to capture any exception generated by GPIO +Zero's code:: + + from gpiozero import * + + led = PWMLED(17) + try: + led.value = 2 + except GPIOZeroError: + print('A GPIO Zero error occurred') + +Since all GPIO Zero's exceptions descend from :exc:`GPIOZeroError`, this will +work. However, certain specific errors have multiple parents. For example, in +the case that an out of range value is passed to :attr:`OutputDevice.value` you +would expect a :exc:`ValueError` to be raised. In fact, a +:exc:`OutputDeviceBadValue` error will be raised. However, note that this +descends from both :exc:`GPIOZeroError` (indirectly) and from :exc:`ValueError` +so you can still do the obvious:: + + from gpiozero import * + + led = PWMLED(17) + try: + led.value = 2 + except ValueError: + print('Bad value specified') + + +Errors +====== + +.. autoexception:: GPIOZeroError + :show-inheritance: + +.. autoexception:: DeviceClosed + :show-inheritance: + +.. autoexception:: BadEventHandler + :show-inheritance: + +.. autoexception:: BadWaitTime + :show-inheritance: + +.. autoexception:: BadQueueLen + :show-inheritance: + +.. autoexception:: BadPinFactory + :show-inheritance: + +.. autoexception:: ZombieThread + :show-inheritance: + +.. autoexception:: CompositeDeviceError + :show-inheritance: + +.. autoexception:: CompositeDeviceBadName + :show-inheritance: + +.. autoexception:: CompositeDeviceBadOrder + :show-inheritance: + +.. autoexception:: CompositeDeviceBadDevice + :show-inheritance: + +.. autoexception:: EnergenieSocketMissing + :show-inheritance: + +.. autoexception:: EnergenieBadSocket + :show-inheritance: + +.. autoexception:: SPIError + :show-inheritance: + +.. autoexception:: SPIBadArgs + :show-inheritance: + +.. autoexception:: SPIBadChannel + :show-inheritance: + +.. autoexception:: SPIFixedClockMode + :show-inheritance: + +.. autoexception:: SPIInvalidClockMode + :show-inheritance: + +.. autoexception:: SPIFixedBitOrder + :show-inheritance: + +.. autoexception:: SPIFixedSelect + :show-inheritance: + +.. autoexception:: SPIFixedWordSize + :show-inheritance: + +.. autoexception:: SPIInvalidWordSize + :show-inheritance: + +.. autoexception:: GPIODeviceError + :show-inheritance: + +.. autoexception:: GPIODeviceClosed + :show-inheritance: + +.. autoexception:: GPIOPinInUse + :show-inheritance: + +.. autoexception:: GPIOPinMissing + :show-inheritance: + +.. autoexception:: InputDeviceError + :show-inheritance: + +.. autoexception:: OutputDeviceError + :show-inheritance: + +.. autoexception:: OutputDeviceBadValue + :show-inheritance: + +.. autoexception:: PinError + :show-inheritance: + +.. autoexception:: PinInvalidFunction + :show-inheritance: + +.. autoexception:: PinInvalidState + :show-inheritance: + +.. autoexception:: PinInvalidPull + :show-inheritance: + +.. autoexception:: PinInvalidEdges + :show-inheritance: + +.. autoexception:: PinInvalidBounce + :show-inheritance: + +.. autoexception:: PinSetInput + :show-inheritance: + +.. autoexception:: PinFixedPull + :show-inheritance: + +.. autoexception:: PinEdgeDetectUnsupported + :show-inheritance: + +.. autoexception:: PinUnsupported + :show-inheritance: + +.. autoexception:: PinSPIUnsupported + :show-inheritance: + +.. autoexception:: PinPWMError + :show-inheritance: + +.. autoexception:: PinPWMUnsupported + :show-inheritance: + +.. autoexception:: PinPWMFixedValue + :show-inheritance: + +.. autoexception:: PinUnknownPi + :show-inheritance: + +.. autoexception:: PinMultiplePins + :show-inheritance: + +.. autoexception:: PinNoPins + :show-inheritance: + +.. autoexception:: PinInvalidPin + :show-inheritance: + +Warnings +======== + +.. autoexception:: GPIOZeroWarning + :show-inheritance: + +.. autoexception:: DistanceSensorNoEcho + :show-inheritance: + +.. autoexception:: SPIWarning + :show-inheritance: + +.. autoexception:: SPISoftwareFallback + :show-inheritance: + +.. autoexception:: PinWarning + :show-inheritance: + +.. autoexception:: PinFactoryFallback + :show-inheritance: + +.. autoexception:: PinNonPhysical + :show-inheritance: + +.. autoexception:: ThresholdOutOfRange + :show-inheritance: + +.. autoexception:: CallbackSetToNone + :show-inheritance: diff --git a/docs/api_fonts.rst b/docs/api_fonts.rst new file mode 100644 index 000000000..24cb2910e --- /dev/null +++ b/docs/api_fonts.rst @@ -0,0 +1,36 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2021 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +=========== +API - Fonts +=========== + +.. module:: gpiozero.fonts + +GPIO Zero includes a concept of "fonts" which is somewhat different to that you +may be familiar with. While a typical printing font determines how a particular +character is rendered on a page, a GPIO Zero font determines how a particular +character is rendered by a series of lights, like LED segments (e.g. with +:class:`~gpiozero.LEDCharDisplay` or :class:`~gpiozero.LEDMultiCharDisplay`). + +As a result, GPIO Zero's fonts are quite crude affairs, being little more than +mappings of characters to tuples of LED states. Still, it helps to have a +"friendly" format for creating such fonts, and in this module the library +provides several routines for this purpose. + +The module itself is typically imported as follows:: + + from gpiozero import fonts + + +Font Parsing +============ + +.. autofunction:: load_font_7seg + +.. autofunction:: load_font_14seg + +.. autofunction:: load_segment_font diff --git a/docs/api_generic.rst b/docs/api_generic.rst new file mode 100644 index 000000000..6f0009573 --- /dev/null +++ b/docs/api_generic.rst @@ -0,0 +1,81 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2021 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +===================== +API - Generic Classes +===================== + +.. module:: gpiozero.devices + +.. currentmodule:: gpiozero + +The GPIO Zero class hierarchy is quite extensive. It contains several base +classes (most of which are documented in their corresponding chapters): + +* :class:`Device` is the root of the hierarchy, implementing base functionality + like :meth:`~Device.close` and context manager handlers. + +* :class:`GPIODevice` represents individual devices that attach to a single + GPIO pin + +* :class:`SPIDevice` represents devices that communicate over an SPI interface + (implemented as four GPIO pins) + +* :class:`InternalDevice` represents devices that are entirely internal to + the Pi (usually operating system related services) + +* :class:`CompositeDevice` represents devices composed of multiple other + devices like HATs + +There are also several `mixin classes`_ for adding important functionality +at numerous points in the hierarchy, which is illustrated below (mixin classes +are represented in purple, while abstract classes are shaded lighter): + +.. image:: images/device_hierarchy.* + +.. _mixin classes: https://en.wikipedia.org/wiki/Mixin + + +Device +====== + +.. autoclass:: Device + :members: close, closed, value, is_active, pin_factory + + +ValuesMixin +=========== + +.. autoclass:: ValuesMixin(...) + :members: + + +SourceMixin +=========== + +.. autoclass:: SourceMixin(...) + :members: + + +SharedMixin +=========== + +.. autoclass:: SharedMixin(...) + :members: _shared_key + + +EventsMixin +=========== + +.. autoclass:: EventsMixin(...) + :members: + + +HoldMixin +========= + +.. autoclass:: HoldMixin(...) + :members: diff --git a/docs/api_info.rst b/docs/api_info.rst new file mode 100644 index 000000000..3a12a2794 --- /dev/null +++ b/docs/api_info.rst @@ -0,0 +1,43 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +==================== +API - Pi Information +==================== + +.. module:: gpiozero.pins.data + +.. currentmodule:: gpiozero + +The GPIO Zero library also contains a database of information about the various +revisions of the Raspberry Pi computer. This is used internally to raise +warnings when non-physical pins are used, or to raise exceptions when +pull-downs are requested on pins with physical pull-up resistors attached. The +following functions and classes can be used to query this database: + + +pi_info +======= + +.. autofunction:: pi_info + + +PiBoardInfo +=========== + +.. autoclass:: PiBoardInfo + + +HeaderInfo +========== + +.. autoclass:: HeaderInfo + + +PinInfo +======= + +.. autoclass:: PinInfo diff --git a/docs/api_input.rst b/docs/api_input.rst new file mode 100644 index 000000000..68435ea7d --- /dev/null +++ b/docs/api_input.rst @@ -0,0 +1,112 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2021 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +=================== +API - Input Devices +=================== + +.. module:: gpiozero.input_devices + +.. currentmodule:: gpiozero + +These input device component interfaces have been provided for simple use of +everyday components. Components must be wired up correctly before use in code. + +.. note:: + + All GPIO pin numbers use Broadcom (BCM) numbering by default. See the + :ref:`pin-numbering` section for more information. + + +Regular Classes +=============== + +The following classes are intended for general use with the devices they +represent. All classes in this section are concrete (not abstract). + + +Button +------ + +.. autoclass:: Button + :members: wait_for_press, wait_for_release, pin, is_pressed, is_held, hold_time, held_time, hold_repeat, pull_up, when_pressed, when_released, when_held, value + + +LineSensor (TRCT5000) +--------------------- + +.. autoclass:: LineSensor + :members: wait_for_line, wait_for_no_line, pin, line_detected, when_line, when_no_line, value + + +MotionSensor (D-SUN PIR) +------------------------ + +.. autoclass:: MotionSensor + :members: wait_for_motion, wait_for_no_motion, pin, motion_detected, when_motion, when_no_motion, value + + +LightSensor (LDR) +----------------- + +.. autoclass:: LightSensor + :members: wait_for_light, wait_for_dark, pin, light_detected, when_light, when_dark, value + + +DistanceSensor (HC-SR04) +------------------------ + +.. autoclass:: DistanceSensor + :members: wait_for_in_range, wait_for_out_of_range, trigger, echo, when_in_range, when_out_of_range, max_distance, distance, threshold_distance, value + + +RotaryEncoder +------------- + +.. autoclass:: RotaryEncoder + :members: wait_for_rotate, wait_for_rotate_clockwise, wait_for_rotate_counter_clockwise, when_rotated, when_rotated_clockwise, when_rotated_counter_clockwise, steps, value, max_steps, threshold_steps, wrap + + +Base Classes +============ + +The classes in the sections above are derived from a series of base classes, +some of which are effectively abstract. The classes form the (partial) +hierarchy displayed in the graph below (abstract classes are shaded lighter +than concrete classes): + +.. image:: images/input_device_hierarchy.* + +The following sections document these base classes for advanced users that wish +to construct classes for their own devices. + + +DigitalInputDevice +------------------ + +.. autoclass:: DigitalInputDevice + :members: wait_for_active, wait_for_inactive, when_activated, when_deactivated, active_time, inactive_time, value + + +SmoothedInputDevice +------------------- + +.. autoclass:: SmoothedInputDevice + :members: is_active, value, threshold, partial, queue_len + + +InputDevice +----------- + +.. autoclass:: InputDevice + :members: pull_up, is_active, value + + +GPIODevice +---------- + +.. autoclass:: GPIODevice + :members: diff --git a/docs/api_internal.rst b/docs/api_internal.rst new file mode 100644 index 000000000..b52f9f6c1 --- /dev/null +++ b/docs/api_internal.rst @@ -0,0 +1,137 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2016-2021 Dave Jones +.. Copyright (c) 2018-2021 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +====================== +API - Internal Devices +====================== + +.. module:: gpiozero.internal_devices + +.. currentmodule:: gpiozero + +GPIO Zero also provides several "internal" devices which represent facilities +provided by the operating system itself. These can be used to react to things +like the time of day, or whether a server is available on the network. + +These devices provide an API similar to and compatible with GPIO devices so that +internal device events can trigger changes to GPIO output devices the way input +devices can. In the same way a :class:`~gpiozero.Button` object is *active* when +it's pressed, and can be used to trigger other devices when its state changes, +a :class:`TimeOfDay` object is *active* during a particular time period. + +Consider the following code in which a :class:`~gpiozero.Button` object is used +to control an :class:`~gpiozero.LED` object:: + + from gpiozero import LED, Button + from signal import pause + + led = LED(2) + btn = Button(3) + + btn.when_pressed = led.on + btn.when_released = led.off + + pause() + +Now consider the following example in which a :class:`TimeOfDay` object is used +to control an :class:`~gpiozero.LED` using the same method:: + + from gpiozero import LED, TimeOfDay + from datetime import time + from signal import pause + + led = LED(2) + tod = TimeOfDay(time(9), time(10)) + + tod.when_activated = led.on + tod.when_deactivated = led.off + + pause() + +Here, rather than the LED being controlled by the press of a button, it's +controlled by the time. When the time reaches 09:00AM, the LED comes on, and at +10:00AM it goes off. + +Like the :class:`~gpiozero.Button` object, internal devices like the +:class:`TimeOfDay` object has :attr:`~TimeOfDay.value`, +:attr:`~TimeOfDay.values`, :attr:`~TimeOfDay.is_active`, +:attr:`~TimeOfDay.when_activated` and :attr:`~TimeOfDay.when_deactivated` +attributes, so alternative methods using the other paradigms would also work. + +.. note:: + Note that although the constructor parameter ``pin_factory`` is available + for internal devices, and is required to be valid, the pin factory chosen + will not make any practical difference. Reading a remote Pi's CPU + temperature, for example, is not currently possible. + + +Regular Classes +=============== + +The following classes are intended for general use with the devices they are +named after. All classes in this section are concrete (not abstract). + + +TimeOfDay +--------- + +.. autoclass:: TimeOfDay + :members: start_time, end_time, utc, value, is_active, when_activated, when_deactivated + + +PingServer +---------- + +.. autoclass:: PingServer + :members: host, value, is_active, when_activated, when_deactivated + + +CPUTemperature +-------------- + +.. autoclass:: CPUTemperature + :members: temperature, value, is_active, when_activated, when_deactivated + + +LoadAverage +----------- + +.. autoclass:: LoadAverage + :members: load_average, value, is_active, when_activated, when_deactivated + + +DiskUsage +--------- + +.. autoclass:: DiskUsage + :members: usage, value, is_active, when_activated, when_deactivated + + +Base Classes +============ + +The classes in the sections above are derived from a series of base classes, +some of which are effectively abstract. The classes form the (partial) +hierarchy displayed in the graph below (abstract classes are shaded lighter +than concrete classes): + +.. image:: images/internal_device_hierarchy.* + +The following sections document these base classes for advanced users that wish +to construct classes for their own devices. + + +PolledInternalDevice +-------------------- + +.. autoclass:: PolledInternalDevice + + +InternalDevice +-------------- + +.. autoclass:: InternalDevice diff --git a/docs/api_output.rst b/docs/api_output.rst new file mode 100644 index 000000000..08284c6c9 --- /dev/null +++ b/docs/api_output.rst @@ -0,0 +1,136 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2021 Dave Jones +.. Copyright (c) 2019 Ben Nuttall +.. Copyright (c) 2016 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +==================== +API - Output Devices +==================== + +.. module:: gpiozero.output_devices + +.. currentmodule:: gpiozero + +These output device component interfaces have been provided for simple use of +everyday components. Components must be wired up correctly before use in code. + +.. note:: + + All GPIO pin numbers use Broadcom (BCM) numbering by default. See the + :ref:`pin-numbering` section for more information. + + +Regular Classes +=============== + +The following classes are intended for general use with the devices they +represent. All classes in this section are concrete (not abstract). + + +LED +--- + +.. autoclass:: LED + :members: on, off, toggle, blink, pin, is_lit, value + + +PWMLED +------ + +.. autoclass:: PWMLED + :members: on, off, toggle, blink, pulse, pin, is_lit, value + + +RGBLED +------ + +.. autoclass:: RGBLED + :members: on, off, toggle, blink, pulse, red, green, blue, is_lit, color, value + + +Buzzer +------ + +.. autoclass:: Buzzer + :members: on, off, toggle, beep, pin, is_active, value + + +TonalBuzzer +----------- + +.. autoclass:: TonalBuzzer + :members: play, stop, octaves, min_tone, mid_tone, max_tone, tone, is_active, value + + +Motor +----- + +.. autoclass:: Motor + :members: forward, backward, reverse, stop, is_active, value + + +PhaseEnableMotor +---------------- + +.. autoclass:: PhaseEnableMotor + :members: forward, backward, reverse, stop, is_active, value + + +Servo +----- + +.. autoclass:: Servo + :members: + + +AngularServo +------------ + +.. autoclass:: AngularServo + :members: angle, max_angle, min_angle, min, max, mid, is_active, value + + +Base Classes +============ + +The classes in the sections above are derived from a series of base classes, +some of which are effectively abstract. The classes form the (partial) +hierarchy displayed in the graph below (abstract classes are shaded lighter +than concrete classes): + +.. image:: images/output_device_hierarchy.* + +The following sections document these base classes for advanced users that wish +to construct classes for their own devices. + + +DigitalOutputDevice +------------------- + +.. autoclass:: DigitalOutputDevice + :members: on, off, blink, value + + +PWMOutputDevice +--------------- + +.. autoclass:: PWMOutputDevice + :members: on, off, blink, pulse, toggle, frequency, is_active, value + + +OutputDevice +------------ + +.. autoclass:: OutputDevice + :members: + + +GPIODevice +---------- + +.. autoclass:: GPIODevice + :members: + :noindex: diff --git a/docs/api_pins.rst b/docs/api_pins.rst new file mode 100644 index 000000000..65edf55fa --- /dev/null +++ b/docs/api_pins.rst @@ -0,0 +1,379 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2023 Dave Jones +.. Copyright (c) 2019-2021 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +========== +API - Pins +========== + +.. module:: gpiozero.pins + +.. currentmodule:: gpiozero + +As of release 1.1, the GPIO Zero library can be roughly divided into two +things: pins and the devices that are connected to them. The majority of the +documentation focuses on devices as pins are below the level that most users +are concerned with. However, some users may wish to take advantage of the +capabilities of alternative GPIO implementations or (in future) use GPIO +extender chips. This is the purpose of the pins portion of the library. + +When you construct a device, you pass in a pin specification. This is passed to +a pin :class:`Factory` which turns it into a :class:`Pin` implementation. The +default factory can be queried (and changed) with :attr:`Device.pin_factory`. +However, all classes (even internal devices) accept a *pin_factory* keyword +argument to their constructors permitting the factory to be overridden on a +per-device basis (the reason for allowing per-device factories is made apparent +in the :doc:`remote_gpio` chapter). + +This is illustrated in the following flow-chart: + +.. image:: images/device_pin_flowchart.* + :align: center + +The default factory is constructed when the first device is initialised; if no +default factory can be constructed (e.g. because no GPIO implementations are +installed, or all of them fail to load for whatever reason), a +:exc:`BadPinFactory` exception will be raised at construction time. + +After importing gpiozero, until constructing a gpiozero device, the pin factory +is :data:`None`, but at the point of first construction the default pin factory +will come into effect: + +.. code-block:: console + + pi@raspberrypi:~ $ python3 + Python 3.7.3 (default, Apr 3 2019, 05:39:12) + [GCC 8.2.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> from gpiozero import Device, LED + >>> print(Device.pin_factory) + None + >>> led = LED(2) + >>> Device.pin_factory + + >>> led.pin_factory + + +As above, on a Raspberry Pi with the RPi.GPIO library installed, (assuming no +environment variables are set), the default pin factory will be +:class:`~gpiozero.pins.rpigpio.RPiGPIOFactory`. + +On a PC (with no pin libraries installed and no environment variables set), +importing will work but attempting to create a device will raise +:exc:`BadPinFactory`: + +.. code-block:: console + + ben@magicman:~ $ python3 + Python 3.6.8 (default, Aug 20 2019, 17:12:48) + [GCC 8.3.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> from gpiozero import Device, LED + >>> print(Device.pin_factory) + None + >>> led = LED(2) + ... + BadPinFactory: Unable to load any default pin factory! + +.. _changing-pin-factory: + +Changing the pin factory +======================== + +The default pin factory can be replaced by specifying a value for the +:envvar:`GPIOZERO_PIN_FACTORY` environment variable. For example: + +.. code-block:: console + + pi@raspberrypi:~ $ GPIOZERO_PIN_FACTORY=native python3 + Python 3.7.3 (default, Apr 3 2019, 05:39:12) + [GCC 8.2.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> from gpiozero import Device + >>> Device._default_pin_factory() + + +To set the :envvar:`GPIOZERO_PIN_FACTORY` for the rest of your session you can +:command:`export` this value: + +.. code-block:: console + + pi@raspberrypi:~ $ export GPIOZERO_PIN_FACTORY=native + pi@raspberrypi:~ $ python3 + Python 3.7.3 (default, Apr 3 2019, 05:39:12) + [GCC 8.2.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import gpiozero + >>> Device._default_pin_factory() + + >>> quit() + pi@raspberrypi:~ $ python3 + Python 3.7.3 (default, Apr 3 2019, 05:39:12) + [GCC 8.2.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import gpiozero + >>> Device._default_pin_factory() + + +If you add the :command:`export` command to your :file:`~/.bashrc` file, you'll +set the default pin factory for all future sessions too. + +If the environment variable is set, the corresponding pin factory will be used, +otherwise each of the four GPIO pin factories will be attempted to be used in +turn. + +The following values, and the corresponding :class:`Factory` and :class:`Pin` +classes are listed in the table below. Factories are listed in the order that +they are tried by default. + ++---------+-----------------------------------------------+-------------------------------------------+ +| Name | Factory class | Pin class | ++=========+===============================================+===========================================+ +| lgpio | :class:`gpiozero.pins.lgpio.LGPIOFactory` | :class:`gpiozero.pins.lgpio.LGPIOPin` | ++---------+-----------------------------------------------+-------------------------------------------+ +| rpigpio | :class:`gpiozero.pins.rpigpio.RPiGPIOFactory` | :class:`gpiozero.pins.rpigpio.RPiGPIOPin` | ++---------+-----------------------------------------------+-------------------------------------------+ +| pigpio | :class:`gpiozero.pins.pigpio.PiGPIOFactory` | :class:`gpiozero.pins.pigpio.PiGPIOPin` | ++---------+-----------------------------------------------+-------------------------------------------+ +| native | :class:`gpiozero.pins.native.NativeFactory` | :class:`gpiozero.pins.native.NativePin` | ++---------+-----------------------------------------------+-------------------------------------------+ + +If you need to change the default pin factory from within a script, either set +:attr:`Device.pin_factory` to the new factory instance to use:: + + from gpiozero.pins.native import NativeFactory + from gpiozero import Device, LED + + Device.pin_factory = NativeFactory() + + # These will now implicitly use NativePin instead of RPiGPIOPin + led1 = LED(16) + led2 = LED(17) + +Or use the *pin_factory* keyword parameter mentioned above:: + + from gpiozero.pins.native import NativeFactory + from gpiozero import LED + + my_factory = NativeFactory() + + # This will use NativePin instead of RPiGPIOPin for led1 + # but led2 will continue to use RPiGPIOPin + led1 = LED(16, pin_factory=my_factory) + led2 = LED(17) + +Certain factories may take default information from additional sources. +For example, to default to creating pins with +:class:`gpiozero.pins.pigpio.PiGPIOPin` on a remote pi called "remote-pi" +you can set the :envvar:`PIGPIO_ADDR` environment variable when running your +script: + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=remote-pi python3 my_script.py + +Like the :envvar:`GPIOZERO_PIN_FACTORY` value, these can be exported from your +:file:`~/.bashrc` script too. + +.. warning:: + + The astute and mischievous reader may note that it is possible to mix + factories, e.g. using :class:`~gpiozero.pins.rpigpio.RPiGPIOFactory` for + one pin, and :class:`~gpiozero.pins.native.NativeFactory` for another. This + is unsupported, and if it results in your script crashing, your components + failing, or your Raspberry Pi turning into an actual raspberry pie, you + have only yourself to blame. + + Sensible uses of multiple pin factories are given in :doc:`remote_gpio`. + + +.. _mock-pins: + +Mock pins +========= + +There's also a :class:`~gpiozero.pins.mock.MockFactory` which generates entirely +fake pins. This was originally intended for GPIO Zero developers who wish to +write tests for devices without having to have the physical device wired in to +their Pi. However, they have also proven useful in developing GPIO Zero scripts +without having a Pi to hand. This pin factory will never be loaded by default; +it must be explicitly specified, either by setting an environment variable or +setting the pin factory within the script. For example: + +.. code-block:: console + + pi@raspberrypi:~ $ GPIOZERO_PIN_FACTORY=mock python3 + +or: + +.. code-block:: python + + from gpiozero import Device, LED + from gpiozero.pins.mock import MockFactory + + Device.pin_factory = MockFactory() + + led = LED(2) + +You can create device objects and inspect their value changing as you'd expect: + +.. code-block:: pycon + + pi@raspberrypi:~ $ GPIOZERO_PIN_FACTORY=mock python3 + Python 3.7.3 (default, Apr 3 2019, 05:39:12) + [GCC 8.2.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> from gpiozero import LED + >>> led = LED(2) + >>> led.value + 0 + >>> led.on() + >>> led.value + 1 + +You can even control pin state changes to simulate device behaviour: + +.. code-block:: pycon + + >>> from gpiozero import LED, Button + + # Construct a couple of devices attached to mock pins 16 and 17, and link the devices + >>> led = LED(17) + >>> btn = Button(16) + >>> led.source = btn + + # Initailly the button isn't "pressed" so the LED should be off + >>> led.value + 0 + + # Drive the pin low (this is what would happen electrically when the button is pressed) + >>> btn.pin.drive_low() + # The LED is now on + >>> led.value + 1 + + >>> btn.pin.drive_high() + # The button is now "released", so the LED should be off again + >>> led.value + 0 + +Several sub-classes of mock pins exist for emulating various other things +(pins that do/don't support PWM, pins that are connected together, pins that +drive high after a delay, etc), for example, you have to use +:class:`~gpiozero.pins.mock.MockPWMPin` to be able to use devices requiring PWM: + +.. code-block:: console + + pi@raspberrypi:~ $ GPIOZERO_PIN_FACTORY=mock GPIOZERO_MOCK_PIN_CLASS=mockpwmpin python3 + +or: + +.. code-block:: python + + from gpiozero import Device, LED + from gpiozero.pins.mock import MockFactory, MockPWMPin + + Device.pin_factory = MockFactory(pin_class=MockPWMPin) + + led = LED(2) + +Interested users are invited to read the `GPIO Zero test suite`_ for further +examples of usage. + +.. _`GPIO Zero test suite`: https://github.com/gpiozero/gpiozero/tree/master/tests + +Base classes +============ + +.. autoclass:: Factory + :members: + +.. autoclass:: Pin + :members: + +.. autoclass:: SPI + :members: + +.. module:: gpiozero.pins.pi + +.. autoclass:: gpiozero.pins.pi.PiFactory + :members: + +.. autoclass:: gpiozero.pins.pi.PiPin + :members: + +.. module:: gpiozero.pins.local + +.. autoclass:: LocalPiFactory + :members: + +.. autoclass:: LocalPiPin + :members: + + +RPi.GPIO +======== + +.. module:: gpiozero.pins.rpigpio + +.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOFactory + +.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin + + +lgpio +===== + +.. module:: gpiozero.pins.lgpio + +.. autoclass:: gpiozero.pins.lgpio.LGPIOFactory + +.. autoclass:: gpiozero.pins.lgpio.LGPIOPin + + +PiGPIO +====== + +.. module:: gpiozero.pins.pigpio + +.. autoclass:: gpiozero.pins.pigpio.PiGPIOFactory + +.. autoclass:: gpiozero.pins.pigpio.PiGPIOPin + + +Native +====== + +.. module:: gpiozero.pins.native + +.. autoclass:: gpiozero.pins.native.NativeFactory + +.. autoclass:: gpiozero.pins.native.NativePin + +.. autoclass:: gpiozero.pins.native.Native2835Pin + +.. autoclass:: gpiozero.pins.native.Native2711Pin + + +Mock +==== + +.. module:: gpiozero.pins.mock + +.. autoclass:: gpiozero.pins.mock.MockFactory + :members: + +.. autoclass:: gpiozero.pins.mock.MockPin + +.. autoclass:: gpiozero.pins.mock.MockPWMPin + +.. autoclass:: gpiozero.pins.mock.MockConnectedPin + +.. autoclass:: gpiozero.pins.mock.MockChargingPin + +.. autoclass:: gpiozero.pins.mock.MockTriggerPin + +.. autoclass:: gpiozero.pins.mock.MockSPIDevice diff --git a/docs/api_spi.rst b/docs/api_spi.rst new file mode 100644 index 000000000..9ed766ae2 --- /dev/null +++ b/docs/api_spi.rst @@ -0,0 +1,207 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2016-2023 Dave Jones +.. Copyright (c) 2017 rgm +.. Copyright (c) 2016 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +================= +API - SPI Devices +================= + +.. module:: gpiozero.spi_devices + +.. currentmodule:: gpiozero + +SPI stands for `Serial Peripheral Interface`_ and is a mechanism allowing +compatible devices to communicate with the Pi. SPI is a four-wire protocol +meaning it usually requires four pins to operate: + +* A "clock" pin which provides timing information. + +* A "MOSI" pin (Master Out, Slave In) which the Pi uses to send information + to the device. + +* A "MISO" pin (Master In, Slave Out) which the Pi uses to receive information + from the device. + +* A "select" pin which the Pi uses to indicate which device it's talking to. + This last pin is necessary because multiple devices can share the clock, + MOSI, and MISO pins, but only one device can be connected to each select + pin. + +The gpiozero library provides two SPI implementations: + +* A software based implementation. This is always available, can use any four + GPIO pins for SPI communication, but is rather slow and won't work with all + devices. + +* A hardware based implementation. This is only available when the SPI kernel + module is loaded, and the Python spidev library is available. It can only use + specific pins for SPI communication (GPIO11=clock, GPIO10=MOSI, GPIO9=MISO, + while GPIO8 is select for device 0 and GPIO7 is select for device 1). + However, it is extremely fast and works with all devices. + +.. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus + + +.. _spi_args: + +SPI keyword args +================ + +When constructing an SPI device there are two schemes for specifying which pins +it is connected to: + +* You can specify *port* and *device* keyword arguments. The *port* parameter + must be 0 (there is only one user-accessible hardware SPI interface on the Pi + using GPIO11 as the clock pin, GPIO10 as the MOSI pin, and GPIO9 as the MISO + pin), while the *device* parameter must be 0 or 1. If *device* is 0, the + select pin will be GPIO8. If *device* is 1, the select pin will be GPIO7. + +* Alternatively you can specify *clock_pin*, *mosi_pin*, *miso_pin*, and + *select_pin* keyword arguments. In this case the pins can be any 4 GPIO pins + (remember that SPI devices can share clock, MOSI, and MISO pins, but not + select pins - the gpiozero library will enforce this restriction). + +You cannot mix these two schemes, i.e. attempting to specify *port* and +*clock_pin* will result in :exc:`SPIBadArgs` being raised. However, you can +omit any arguments from either scheme. The defaults are: + +* *port* and *device* both default to 0. + +* *clock_pin* defaults to 11, *mosi_pin* defaults to 10, *miso_pin* defaults + to 9, and *select_pin* defaults to 8. + +* As with other GPIO based devices you can optionally specify a *pin_factory* + argument overriding the default pin factory (see :doc:`api_pins` for more + information). + +Hence the following constructors are all equivalent:: + + from gpiozero import MCP3008 + + MCP3008(channel=0) + MCP3008(channel=0, device=0) + MCP3008(channel=0, port=0, device=0) + MCP3008(channel=0, select_pin=8) + MCP3008(channel=0, clock_pin=11, mosi_pin=10, miso_pin=9, select_pin=8) + +Note that the defaults describe equivalent sets of pins and that these pins are +compatible with the hardware implementation. Regardless of which scheme you +use, gpiozero will attempt to use the hardware implementation if it is +available and if the selected pins are compatible, falling back to the software +implementation if not. + + +Analog to Digital Converters (ADC) +================================== + +The following classes are intended for general use with the integrated circuits +they are named after. All classes in this section are concrete (not abstract). + + +MCP3001 +------- + +.. autoclass:: MCP3001 + :members: value + + +MCP3002 +------- + +.. autoclass:: MCP3002 + :members: channel, value, differential + + +MCP3004 +------- + +.. autoclass:: MCP3004 + :members: channel, value, differential + + +MCP3008 +------- + +.. autoclass:: MCP3008 + :members: channel, value, differential + + +MCP3201 +------- + +.. autoclass:: MCP3201 + :members: value + + +MCP3202 +------- + +.. autoclass:: MCP3202 + :members: channel, value, differential + + +MCP3204 +------- + +.. autoclass:: MCP3204 + :members: channel, value, differential + + +MCP3208 +------- + +.. autoclass:: MCP3208 + :members: channel, value, differential + + +MCP3301 +------- + +.. autoclass:: MCP3301 + :members: value + + +MCP3302 +------- + +.. autoclass:: MCP3302 + :members: channel, value, differential + + +MCP3304 +------- + +.. autoclass:: MCP3304 + :members: channel, value, differential + + +Base Classes +============ + +The classes in the sections above are derived from a series of base classes, +some of which are effectively abstract. The classes form the (partial) +hierarchy displayed in the graph below (abstract classes are shaded lighter +than concrete classes): + +.. image:: images/spi_device_hierarchy.* + +The following sections document these base classes for advanced users that wish +to construct classes for their own devices. + + +AnalogInputDevice +----------------- + +.. autoclass:: AnalogInputDevice + :members: + + +SPIDevice +--------- + +.. autoclass:: SPIDevice + :members: diff --git a/docs/api_tones.rst b/docs/api_tones.rst new file mode 100644 index 000000000..9e48751e8 --- /dev/null +++ b/docs/api_tones.rst @@ -0,0 +1,24 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2019-2023 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +=========== +API - Tones +=========== + +.. module:: gpiozero.tones + +GPIO Zero includes a :class:`Tone` class intended for use with the +:class:`~gpiozero.TonalBuzzer`. This class is in the ``tones`` module of GPIO +Zero and is typically imported as follows:: + + from gpiozero.tones import Tone + + +Tone +==== + +.. autoclass:: Tone + :members: diff --git a/docs/api_tools.rst b/docs/api_tools.rst new file mode 100644 index 000000000..27e2d2702 --- /dev/null +++ b/docs/api_tools.rst @@ -0,0 +1,83 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2016-2023 Dave Jones +.. Copyright (c) 2017-2019 Ben Nuttall +.. Copyright (c) 2016 Edward Betts +.. Copyright (c) 2016 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +========================= +API - Device Source Tools +========================= + +.. module:: gpiozero.tools + +GPIO Zero includes several utility routines which are intended to be used with +the :doc:`source_values` attributes common to most devices in the library. +These utility routines are in the ``tools`` module of GPIO Zero and are +typically imported as follows:: + + from gpiozero.tools import scaled, negated, all_values + +Given that :attr:`~gpiozero.SourceMixin.source` and +:attr:`~gpiozero.ValuesMixin.values` deal with infinite iterators, another +excellent source of utilities is the :mod:`itertools` module in the standard +library. + +Single source conversions +========================= + +.. autofunction:: absoluted + +.. autofunction:: booleanized + +.. autofunction:: clamped + +.. autofunction:: inverted + +.. autofunction:: negated + +.. autofunction:: post_delayed + +.. autofunction:: post_periodic_filtered + +.. autofunction:: pre_delayed + +.. autofunction:: pre_periodic_filtered + +.. autofunction:: quantized + +.. autofunction:: queued + +.. autofunction:: smoothed + +.. autofunction:: scaled + +Combining sources +================= + +.. autofunction:: all_values + +.. autofunction:: any_values + +.. autofunction:: averaged + +.. autofunction:: multiplied + +.. autofunction:: summed + +.. autofunction:: zip_values + +Artificial sources +================== + +.. autofunction:: alternating_values + +.. autofunction:: cos_values + +.. autofunction:: ramping_values + +.. autofunction:: random_values + +.. autofunction:: sin_values diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 000000000..0dd429eb1 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,489 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2024 Dave Jones +.. Copyright (c) 2016-2021 Ben Nuttall +.. Copyright (c) 2016 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +========= +Changelog +========= + +.. currentmodule:: gpiozero + + +Release 2.0.1 (2024-02-15) +========================== + +* Fixed Python 3.12 compatibility, and clarify that 3.9 is the lowest supported + version in our CI configuration (`#1113`_) + +.. _#1113: https://github.com/gpiozero/gpiozero/issues/1113 + + +Release 2.0 (2023-09-12) +======================== + +* Removed Python 2.x support; many thanks to Fangchen Li for a substantial + amount of work on this! (`#799`_ `#896`_) +* Removed RPIO pin implementation +* Made :class:`gpiozero.pins.lgpio.LGPIOFactory` the default factory; the + former default, :class:`gpiozero.pins.rpigpio.RPiGPIOFactory`, is now the + second place preference +* Added :doc:`compat` chapter +* Added :program:`pintest` utility +* Added Raspberry Pi 5 board data + +.. _#799: https://github.com/gpiozero/gpiozero/issues/799 +.. _#896: https://github.com/gpiozero/gpiozero/issues/896 + + +Release 1.6.2 (2021-03-18) +========================== + +* Correct docs referring to 1.6.0 as the last version supporting Python 2 + +.. warning:: + + This is the last release to support Python 2 + +Release 1.6.1 (2021-03-17) +========================== + +* Fix missing font files for 7-segment displays + +Release 1.6.0 (2021-03-14) +========================== + +* Added :class:`RotaryEncoder` class (thanks to Paulo Mateus) (`#482`_, `#928`_) +* Added support for multi-segment character displays with + :class:`LEDCharDisplay` and :class:`LEDMultiCharDisplay` along with "font" + support using :class:`LEDCharFont` (thanks to Martin O'Hanlon) (`#357`_, + `#485`_, `#488`_, `#493`_, `#930`_) +* Added :class:`Pibrella` class (thanks to Carl Monk) (`#773`_, `#798`_) +* Added :class:`TrafficpHat` class (thanks to Ryan Walmsley) (`#845`_, `#846`_) +* Added support for the `lgpio`_ library as a pin factory + (:class:`~gpiozero.pins.lgpio.LGPIOFactory`) (thanks to Joan for lg) (`#927`_) +* Allow :class:`Motor` to pass :attr:`~Device.pin_factory` to its child + :class:`OutputDevice` objects (thanks to Yisrael Dov Lebow) (`#792`_) +* Small SPI exception fix (thanks to Maksim Levental) (`#762`_) +* Warn users when using default pin factory for Servos and Distance Sensors + (thanks to Sofiia Kosovan and Daniele Procida at the EuroPython sprints) + (`#780`_, `#781`_) +* Added :attr:`~Servo.pulse_width` property to :class:`Servo` (suggested by + Daniele Procida at the PyCon UK sprints) (`#795`_, `#797`_) +* Added event-driven functionality to :doc:`internal devices ` + (`#941`_) +* Allowed :class:`Energenie` sockets preserve their state on construction + (thanks to Jack Wearden) (`#865`_) +* Added source tools :func:`~gpiozero.tools.scaled_half` and + :func:`~gpiozero.tools.scaled_full` +* Added complete Pi 4 support to :class:`~gpiozero.pins.native.NativeFactory` + (thanks to Andrew Scheller) (`#920`_, `#929`_, `#940`_) +* Updated add-on boards to use BOARD numbering (`#349`_, `#860`_) +* Fixed :class:`ButtonBoard` release events (`#761`_) +* Add ASCII art diagrams to :program:`pinout` for Pi 400 and CM4 (`#932`_) +* Cleaned up software SPI (thanks to Andrew Scheller and Kyle Morgan) (`#777`_, + `#895`_, `#900`_) +* Added USB3 and Ethernet speed attributes to :func:`pi_info` +* Various docs updates + +.. _lgpio: http://abyz.me.uk/lg/py_lgpio.html +.. _#482: https://github.com/gpiozero/gpiozero/issues/482 +.. _#928: https://github.com/gpiozero/gpiozero/issues/928 +.. _#357: https://github.com/gpiozero/gpiozero/issues/357 +.. _#485: https://github.com/gpiozero/gpiozero/issues/485 +.. _#488: https://github.com/gpiozero/gpiozero/issues/488 +.. _#493: https://github.com/gpiozero/gpiozero/issues/493 +.. _#930: https://github.com/gpiozero/gpiozero/issues/930 +.. _#773: https://github.com/gpiozero/gpiozero/issues/773 +.. _#798: https://github.com/gpiozero/gpiozero/issues/798 +.. _#845: https://github.com/gpiozero/gpiozero/issues/845 +.. _#846: https://github.com/gpiozero/gpiozero/issues/846 +.. _#927: https://github.com/gpiozero/gpiozero/issues/927 +.. _#792: https://github.com/gpiozero/gpiozero/issues/792 +.. _#762: https://github.com/gpiozero/gpiozero/issues/762 +.. _#780: https://github.com/gpiozero/gpiozero/issues/780 +.. _#781: https://github.com/gpiozero/gpiozero/issues/781 +.. _#795: https://github.com/gpiozero/gpiozero/issues/795 +.. _#797: https://github.com/gpiozero/gpiozero/issues/797 +.. _#941: https://github.com/gpiozero/gpiozero/issues/941 +.. _#865: https://github.com/gpiozero/gpiozero/issues/865 +.. _#920: https://github.com/gpiozero/gpiozero/issues/920 +.. _#929: https://github.com/gpiozero/gpiozero/issues/929 +.. _#940: https://github.com/gpiozero/gpiozero/issues/940 +.. _#939: https://github.com/gpiozero/gpiozero/issues/939 +.. _#349: https://github.com/gpiozero/gpiozero/issues/349 +.. _#860: https://github.com/gpiozero/gpiozero/issues/860 +.. _#761: https://github.com/gpiozero/gpiozero/issues/761 +.. _#932: https://github.com/gpiozero/gpiozero/issues/932 +.. _#777: https://github.com/gpiozero/gpiozero/issues/777 +.. _#895: https://github.com/gpiozero/gpiozero/issues/895 +.. _#900: https://github.com/gpiozero/gpiozero/issues/900 + +Release 1.5.1 (2019-06-24) +========================== + +* Added Raspberry Pi 4 data for :func:`pi_info` and :program:`pinout` +* Minor docs updates + +Release 1.5.0 (2019-02-12) +========================== + +* Introduced pin event timing to increase accuracy of certain devices such as + the HC-SR04 :class:`DistanceSensor`. (`#664`_, `#665`_) +* Further improvements to :class:`DistanceSensor` (ignoring missed edges). + (`#719`_) +* Allow :attr:`~Device.source` to take a device object as well as + :attr:`~Device.values` or other ``values``. See :doc:`Source/Values + `. (`#640`_) +* Added internal device classes :class:`LoadAverage` and :class:`DiskUsage` + (thanks to Jeevan M R for the latter). (`#532`_, `#714`_) +* Added support for `colorzero`_ with :class:`RGBLED` (this adds a new + dependency). (`#655`_) +* Added :class:`TonalBuzzer` with :class:`~tones.Tone` API for specifying frequencies + raw or via MIDI or musical notes. (`#681`_, `#717`_) +* Added :class:`PiHutXmasTree`. (`#502`_) +* Added :class:`PumpkinPi` and :class:`JamHat` (thanks to Claire Pollard). + (`#680`_, `#681`_, `#717`_) +* Ensured gpiozero can be imported without a valid pin factory set. (`#591`_, + `#713`_) +* Reduced import time by not computing default pin factory at the point of + import. (`#675`_, `#722`_) +* Added support for various pin numbering mechanisms. (`#470`_) +* :class:`Motor` instances now use :class:`DigitalOutputDevice` for non-PWM + pins. +* Allow non-PWM use of :class:`Robot`. (`#481`_) +* Added optional ``enable`` init param to :class:`Motor`. (`#366`_) +* Added ``--xyz`` option to :program:`pinout` command line tool to open + `pinout.xyz`_ in a web browser. (`#604`_) +* Added 3B+, 3A+ and CM3+ to Pi model data. (`#627`_, `#704`_) +* Minor improvements to :class:`Energenie`, thanks to Steve Amor. (`#629`_, + `#634`_) +* Allow :class:`SmoothedInputDevice`, :class:`LightSensor` and + :class:`MotionSensor` to have pull-up configured. (`#652`_) +* Allow input devices to be pulled up or down externally, thanks to Philippe + Muller. (`#593`_, `#658`_) +* Minor changes to support Python 3.7, thanks to Russel Winder and Rick Ansell. + (`#666`_, `#668`_, `#669`_, `#671`_, `#673`_) +* Added :func:`~tools.zip_values` source tool. +* Correct row/col numbering logic in :class:`PinInfo`. (`#674`_) +* Many additional tests, and other improvements to the test suite. +* Many documentation corrections, additions and clarifications. +* Automatic documentation class hierarchy diagram generation. +* Automatic copyright attribution in source files. + +.. _#640: https://github.com/gpiozero/gpiozero/issues/640 +.. _#664: https://github.com/gpiozero/gpiozero/issues/664 +.. _#665: https://github.com/gpiozero/gpiozero/issues/665 +.. _#719: https://github.com/gpiozero/gpiozero/issues/719 +.. _#532: https://github.com/gpiozero/gpiozero/issues/532 +.. _#714: https://github.com/gpiozero/gpiozero/issues/714 +.. _colorzero: https://colorzero.readthedocs.io/en/stable +.. _#655: https://github.com/gpiozero/gpiozero/issues/655 +.. _#681: https://github.com/gpiozero/gpiozero/issues/681 +.. _#717: https://github.com/gpiozero/gpiozero/issues/717 +.. _#680: https://github.com/gpiozero/gpiozero/issues/680 +.. _#502: https://github.com/gpiozero/gpiozero/issues/502 +.. _#591: https://github.com/gpiozero/gpiozero/issues/591 +.. _#713: https://github.com/gpiozero/gpiozero/issues/713 +.. _#675: https://github.com/gpiozero/gpiozero/issues/675 +.. _#722: https://github.com/gpiozero/gpiozero/issues/722 +.. _#470: https://github.com/gpiozero/gpiozero/issues/470 +.. _#481: https://github.com/gpiozero/gpiozero/issues/481 +.. _#366: https://github.com/gpiozero/gpiozero/issues/366 +.. _pinout.xyz: https://pinout.xyz +.. _#604: https://github.com/gpiozero/gpiozero/issues/604 +.. _#627: https://github.com/gpiozero/gpiozero/issues/627 +.. _#704: https://github.com/gpiozero/gpiozero/issues/704 +.. _#629: https://github.com/gpiozero/gpiozero/issues/629 +.. _#634: https://github.com/gpiozero/gpiozero/issues/634 +.. _#652: https://github.com/gpiozero/gpiozero/issues/652 +.. _#593: https://github.com/gpiozero/gpiozero/issues/593 +.. _#658: https://github.com/gpiozero/gpiozero/issues/658 +.. _#666: https://github.com/gpiozero/gpiozero/issues/666 +.. _#668: https://github.com/gpiozero/gpiozero/issues/668 +.. _#669: https://github.com/gpiozero/gpiozero/issues/669 +.. _#671: https://github.com/gpiozero/gpiozero/issues/671 +.. _#673: https://github.com/gpiozero/gpiozero/issues/673 +.. _#674: https://github.com/gpiozero/gpiozero/issues/674 + +Release 1.4.1 (2018-02-20) +========================== + +This release is mostly bug-fixes, but a few enhancements have made it in too: + +* Added ``curve_left`` and ``curve_right`` parameters to :meth:`Robot.forward` + and :meth:`Robot.backward`. (`#306`_ and `#619`_) +* Fixed :class:`DistanceSensor` returning incorrect readings after a long + pause, and added a lock to ensure multiple distance sensors can operate + simultaneously in a single project. (`#584`_, `#595`_, `#617`_, `#618`_) +* Added support for phase/enable motor drivers with :class:`PhaseEnableMotor`, + :class:`PhaseEnableRobot`, and descendants, thanks to Ian Harcombe! + (`#386`_) +* A variety of other minor enhancements, largely thanks to Andrew Scheller! + (`#479`_, `#489`_, `#491`_, `#492`_) + +.. _#306: https://github.com/gpiozero/gpiozero/issues/306 +.. _#386: https://github.com/gpiozero/gpiozero/issues/386 +.. _#479: https://github.com/gpiozero/gpiozero/issues/479 +.. _#489: https://github.com/gpiozero/gpiozero/issues/489 +.. _#491: https://github.com/gpiozero/gpiozero/issues/491 +.. _#492: https://github.com/gpiozero/gpiozero/issues/492 +.. _#584: https://github.com/gpiozero/gpiozero/issues/584 +.. _#595: https://github.com/gpiozero/gpiozero/issues/595 +.. _#617: https://github.com/gpiozero/gpiozero/issues/617 +.. _#618: https://github.com/gpiozero/gpiozero/issues/618 +.. _#619: https://github.com/gpiozero/gpiozero/issues/619 + +Release 1.4.0 (2017-07-26) +========================== + +* Pin factory is now :ref:`configurable from device constructors + ` as well as command line. NOTE: this is a backwards + incompatible change for manual pin construction but it's hoped this is + (currently) a sufficiently rare use case that this won't affect too many + people and the benefits of the new system warrant such a change, i.e. the + ability to use remote pin factories with HAT classes that don't accept pin + assignations (`#279`_) +* Major work on SPI, primarily to support remote hardware SPI (`#421`_, + `#459`_, `#465`_, `#468`_, `#575`_) +* Pin reservation now works properly between GPIO and SPI devices (`#459`_, + `#468`_) +* Lots of work on the documentation: :doc:`source/values chapter + `, better charts, more recipes, :doc:`remote GPIO + configuration `, mock pins, better PDF output (`#484`_, `#469`_, + `#523`_, `#520`_, `#434`_, `#565`_, `#576`_) +* Support for :class:`StatusZero` and :class:`StatusBoard` HATs (`#558`_) +* Added :program:`pinout` command line tool to provide a simple + reference to the GPIO layout and information about the associated Pi + (`#497`_, `#504`_) thanks to Stewart Adcock for the initial work +* :func:`pi_info` made more lenient for new (unknown) Pi models (`#529`_) +* Fixed a variety of packaging issues (`#535`_, `#518`_, `#519`_) +* Improved text in factory fallback warnings (`#572`_) + +.. _#279: https://github.com/gpiozero/gpiozero/issues/279 +.. _#421: https://github.com/gpiozero/gpiozero/issues/421 +.. _#434: https://github.com/gpiozero/gpiozero/issues/434 +.. _#459: https://github.com/gpiozero/gpiozero/issues/459 +.. _#465: https://github.com/gpiozero/gpiozero/issues/465 +.. _#468: https://github.com/gpiozero/gpiozero/issues/468 +.. _#469: https://github.com/gpiozero/gpiozero/issues/469 +.. _#484: https://github.com/gpiozero/gpiozero/issues/484 +.. _#497: https://github.com/gpiozero/gpiozero/issues/497 +.. _#504: https://github.com/gpiozero/gpiozero/issues/504 +.. _#518: https://github.com/gpiozero/gpiozero/issues/518 +.. _#519: https://github.com/gpiozero/gpiozero/issues/519 +.. _#520: https://github.com/gpiozero/gpiozero/issues/520 +.. _#523: https://github.com/gpiozero/gpiozero/issues/523 +.. _#529: https://github.com/gpiozero/gpiozero/issues/529 +.. _#535: https://github.com/gpiozero/gpiozero/issues/535 +.. _#558: https://github.com/gpiozero/gpiozero/issues/558 +.. _#565: https://github.com/gpiozero/gpiozero/issues/565 +.. _#572: https://github.com/gpiozero/gpiozero/issues/572 +.. _#575: https://github.com/gpiozero/gpiozero/issues/575 +.. _#576: https://github.com/gpiozero/gpiozero/issues/576 + +Release 1.3.2 (2017-03-03) +========================== + +* Added new Pi models to stop :func:`pi_info` breaking +* Fix issue with :func:`pi_info` breaking on unknown Pi models + +Release 1.3.1 (2016-08-31 ... later) +==================================== + +* Fixed hardware SPI support which Dave broke in 1.3.0. Sorry! +* Some minor docs changes + +Release 1.3.0 (2016-08-31) +========================== + +* Added :class:`ButtonBoard` for reading multiple buttons in a single + class (`#340`_) +* Added :class:`Servo` and :class:`AngularServo` classes for controlling + simple servo motors (`#248`_) +* Lots of work on supporting easier use of internal and third-party pin + implementations (`#359`_) +* :class:`Robot` now has a proper :attr:`~Robot.value` attribute (`#305`_) +* Added :class:`CPUTemperature` as another demo of "internal" devices (`#294`_) +* A temporary work-around for an issue with :class:`DistanceSensor` was + included but a full fix is in the works (`#385`_) +* More work on the documentation (`#320`_, `#295`_, `#289`_, etc.) + +Not quite as much as we'd hoped to get done this time, but we're rushing to +make a Raspbian freeze. As always, thanks to the community - your suggestions +and PRs have been brilliant and even if we don't take stuff exactly as is, it's +always great to see your ideas. Onto 1.4! + +.. _#248: https://github.com/gpiozero/gpiozero/issues/248 +.. _#289: https://github.com/gpiozero/gpiozero/issues/289 +.. _#294: https://github.com/gpiozero/gpiozero/issues/294 +.. _#295: https://github.com/gpiozero/gpiozero/issues/295 +.. _#305: https://github.com/gpiozero/gpiozero/issues/305 +.. _#320: https://github.com/gpiozero/gpiozero/issues/320 +.. _#340: https://github.com/gpiozero/gpiozero/issues/340 +.. _#359: https://github.com/gpiozero/gpiozero/issues/359 +.. _#385: https://github.com/gpiozero/gpiozero/issues/385 + +Release 1.2.0 (2016-04-10) +========================== + +* Added :class:`Energenie` class for controlling Energenie plugs (`#69`_) +* Added :class:`LineSensor` class for single line-sensors (`#109`_) +* Added :class:`DistanceSensor` class for HC-SR04 ultra-sonic sensors (`#114`_) +* Added :class:`SnowPi` class for the Ryanteck Snow-pi board (`#130`_) +* Added :attr:`~Button.when_held` (and related properties) to :class:`Button` + (`#115`_) +* Fixed issues with installing GPIO Zero for python 3 on Raspbian Wheezy + releases (`#140`_) +* Added support for lots of ADC chips (MCP3xxx family) (`#162`_) - many thanks + to pcopa and lurch! +* Added support for pigpiod as a pin implementation with + :class:`~gpiozero.pins.pigpio.PiGPIOPin` (`#180`_) +* Many refinements to the base classes mean more consistency in composite + devices and several bugs squashed (`#164`_, `#175`_, `#182`_, `#189`_, + `#193`_, `#229`_) +* GPIO Zero is now aware of what sort of Pi it's running on via :func:`pi_info` + and has a fairly extensive database of Pi information which it uses to + determine when users request impossible things (like pull-down on a pin with + a physical pull-up resistor) (`#222`_) +* The source/values system was enhanced to ensure normal usage doesn't stress + the CPU and lots of utilities were added (`#181`_, `#251`_) + +And I'll just add a note of thanks to the many people in the community who +contributed to this release: we've had some great PRs, suggestions, and bug +reports in this version. Of particular note: + +* Schelto van Doorn was instrumental in adding support for numerous ADC chips +* Alex Eames generously donated a RasPiO Analog board which was extremely + useful in developing the software SPI interface (and testing the ADC support) +* Andrew Scheller squashed several dozen bugs (usually a day or so after Dave + had introduced them ;) + +As always, many thanks to the whole community - we look forward to hearing from +you more in 1.3! + +.. _#69: https://github.com/gpiozero/gpiozero/issues/69 +.. _#109: https://github.com/gpiozero/gpiozero/issues/109 +.. _#114: https://github.com/gpiozero/gpiozero/issues/114 +.. _#115: https://github.com/gpiozero/gpiozero/issues/115 +.. _#130: https://github.com/gpiozero/gpiozero/issues/130 +.. _#140: https://github.com/gpiozero/gpiozero/issues/140 +.. _#162: https://github.com/gpiozero/gpiozero/issues/162 +.. _#164: https://github.com/gpiozero/gpiozero/issues/164 +.. _#175: https://github.com/gpiozero/gpiozero/issues/175 +.. _#180: https://github.com/gpiozero/gpiozero/issues/180 +.. _#181: https://github.com/gpiozero/gpiozero/issues/181 +.. _#182: https://github.com/gpiozero/gpiozero/issues/182 +.. _#189: https://github.com/gpiozero/gpiozero/issues/189 +.. _#193: https://github.com/gpiozero/gpiozero/issues/193 +.. _#222: https://github.com/gpiozero/gpiozero/issues/222 +.. _#229: https://github.com/gpiozero/gpiozero/issues/229 +.. _#251: https://github.com/gpiozero/gpiozero/issues/251 + +Release 1.1.0 (2016-02-08) +========================== + +* Documentation converted to reST and expanded to include generic classes + and several more recipes (`#80`_, `#82`_, `#101`_, `#119`_, `#135`_, `#168`_) +* New :class:`CamJamKitRobot` class with the pre-defined motor pins for the new + CamJam EduKit +* New :class:`LEDBarGraph` class (many thanks to Martin O'Hanlon!) (`#126`_, + `#176`_) +* New :class:`Pin` implementation abstracts out the concept of a GPIO pin + paving the way for alternate library support and IO extenders in future + (`#141`_) +* New :meth:`LEDBoard.blink` method which works properly even when background + is set to ``False`` (`#94`_, `#161`_) +* New :meth:`RGBLED.blink` method which implements (rudimentary) color fading + too! (`#135`_, `#174`_) +* New ``initial_value`` attribute on :class:`OutputDevice` ensures consistent + behaviour on construction (`#118`_) +* New ``active_high`` attribute on :class:`PWMOutputDevice` and :class:`RGBLED` + allows use of common anode devices (`#143`_, `#154`_) +* Loads of new ADC chips supported (many thanks to GitHub user pcopa!) + (`#150`_) + +.. _#80: https://github.com/gpiozero/gpiozero/issues/80 +.. _#82: https://github.com/gpiozero/gpiozero/issues/82 +.. _#94: https://github.com/gpiozero/gpiozero/issues/94 +.. _#101: https://github.com/gpiozero/gpiozero/issues/101 +.. _#118: https://github.com/gpiozero/gpiozero/issues/118 +.. _#119: https://github.com/gpiozero/gpiozero/issues/119 +.. _#126: https://github.com/gpiozero/gpiozero/issues/126 +.. _#135: https://github.com/gpiozero/gpiozero/issues/135 +.. _#141: https://github.com/gpiozero/gpiozero/issues/141 +.. _#143: https://github.com/gpiozero/gpiozero/issues/143 +.. _#150: https://github.com/gpiozero/gpiozero/issues/150 +.. _#154: https://github.com/gpiozero/gpiozero/issues/154 +.. _#161: https://github.com/gpiozero/gpiozero/issues/161 +.. _#168: https://github.com/gpiozero/gpiozero/issues/168 +.. _#174: https://github.com/gpiozero/gpiozero/issues/174 +.. _#176: https://github.com/gpiozero/gpiozero/issues/176 + +Release 1.0.0 (2015-11-16) +========================== + +* Debian packaging added (`#44`_) +* :class:`PWMLED` class added (`#58`_) +* ``TemperatureSensor`` removed pending further work (`#93`_) +* :meth:`Buzzer.beep` alias method added (`#75`_) +* :class:`Motor` PWM devices exposed, and :class:`Robot` motor devices exposed + (`#107`_) + +.. _#44: https://github.com/gpiozero/gpiozero/issues/44 +.. _#58: https://github.com/gpiozero/gpiozero/issues/58 +.. _#93: https://github.com/gpiozero/gpiozero/issues/93 +.. _#75: https://github.com/gpiozero/gpiozero/issues/75 +.. _#107: https://github.com/gpiozero/gpiozero/issues/107 + +Release 0.9.0 (2015-10-25) +========================== + +Fourth public beta + +* Added source and values properties to all relevant classes (`#76`_) +* Fix names of parameters in :class:`Motor` constructor (`#79`_) +* Added wrappers for LED groups on add-on boards (`#81`_) + +.. _#76: https://github.com/gpiozero/gpiozero/issues/76 +.. _#79: https://github.com/gpiozero/gpiozero/issues/79 +.. _#81: https://github.com/gpiozero/gpiozero/issues/81 + +Release 0.8.0 (2015-10-16) +========================== + +Third public beta + +* Added generic :class:`AnalogInputDevice` class along with specific classes + for the :class:`MCP3008` and :class:`MCP3004` (`#41`_) +* Fixed :meth:`DigitalOutputDevice.blink` (`#57`_) + +.. _#41: https://github.com/gpiozero/gpiozero/issues/41 +.. _#57: https://github.com/gpiozero/gpiozero/issues/57 + +Release 0.7.0 (2015-10-09) +========================== + +Second public beta + +Release 0.6.0 (2015-09-28) +========================== + +First public beta + +Release 0.5.0 (2015-09-24) +========================== + +Release 0.4.0 (2015-09-23) +========================== + +Release 0.3.0 (2015-09-22) +========================== + +Release 0.2.0 (2015-09-21) +========================== + +Initial release diff --git a/docs/cli_env.rst b/docs/cli_env.rst new file mode 100644 index 000000000..4cfec6ed5 --- /dev/null +++ b/docs/cli_env.rst @@ -0,0 +1,38 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2023 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +===================== +Environment Variables +===================== + +All utilities provided by GPIO Zero accept the following environment variables: + +.. envvar:: GPIOZERO_PIN_FACTORY + + The library to use when communicating with the GPIO pins. Defaults to + attempting to load lgpio, then RPi.GPIO, then pigpio, and finally uses a + native Python implementation. Valid values include "lgpio", "rpigpio", + "pigpio", "native", and "mock". The latter is most useful on non-Pi + platforms as it emulates a Raspberry Pi model 3B (by default). + +.. envvar:: PIGPIO_ADDR + + The hostname of the Raspberry Pi the pigpio library should attempt to + connect to (if the pigpio pin factory is being used). Defaults to + ``localhost``. + +.. envvar:: PIGPIO_PORT + + The port number the pigpio library should attempt to connect to (if the + pigpio pin factory is being used). Defaults to ``8888``. + + +.. only:: builder_man + + See Also + -------- + + :manpage:`pinout(1)`, :manpage:`pintest(1)` diff --git a/docs/cli_pinout.rst b/docs/cli_pinout.rst new file mode 100644 index 000000000..0484e23eb --- /dev/null +++ b/docs/cli_pinout.rst @@ -0,0 +1,187 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2017-2018 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +pinout +====== + +A utility for querying GPIO pin-out information. + +.. only:: not builder_man + + .. image:: images/pinout_pi3.png + :alt: A screenshot of the output from pinout. In a terminal window, a + description of the board is shown at the top, followed by a + colorful ASCII art rendition of the board, and finally a + color-matched list of pins on the GPIO header. + + +Synopsis +-------- + +:: + + pinout [-h] [-r REVISION] [-c] [-m] [-x] + + +Description +----------- + +A utility for querying Raspberry Pi GPIO pin-out information. Running +:program:`pinout` on its own will output a board diagram, and GPIO header +diagram for the current Raspberry Pi. It is also possible to manually specify a +revision of Pi, or (by :doc:`remote_gpio`) to output information about a +remote Pi. + + +Options +------- + +.. program:: pinout + +.. option:: -h, --help + + Show a help message and exit + +.. option:: -r REVISION, --revision REVISION + + Specifies a particular Raspberry Pi board revision code. The default is to + autodetect revision of current device by reading :file:`/proc/cpuinfo` + +.. option:: -c, --color + + Force colored output (by default, the output will include ANSI color codes + if run in a color-capable terminal). See also :option:`pinout --monochrome` + +.. option:: -m, --monochrome + + Force monochrome output. See also :option:`pinout --color` + +.. option:: -x, --xyz + + Open `pinout.xyz`_ in the default web browser + + +Examples +-------- + +To output information about the current Raspberry Pi: + +.. code-block:: console + + $ pinout + +For a Raspberry Pi model 3B, this will output something like the following: + +.. code-block:: none + + Description : Raspberry Pi 3B rev 1.2 + Revision : a02082 + SoC : BCM2837 + RAM : 1GB + Storage : MicroSD + USB ports : 4 (of which 0 USB3) + Ethernet ports : 1 (100Mbps max. speed) + Wi-fi : True + Bluetooth : True + Camera ports (CSI) : 1 + Display ports (DSI): 1 + + ,--------------------------------. + | oooooooooooooooooooo J8 +==== + | 1ooooooooooooooooooo | USB + | +==== + | Pi Model 3B V1.2 | + | |D +---+ +==== + | |S |SoC| | USB + | |I +---+ +==== + | |0 C| | + | S| +====== + | I| |A| | Net + | pwr |HDMI| 0| |u| +====== + `-| |------| |-----|x|--------' + + J8: + 3V3 (1) (2) 5V + GPIO2 (3) (4) 5V + GPIO3 (5) (6) GND + GPIO4 (7) (8) GPIO14 + GND (9) (10) GPIO15 + GPIO17 (11) (12) GPIO18 + GPIO27 (13) (14) GND + GPIO22 (15) (16) GPIO23 + 3V3 (17) (18) GPIO24 + GPIO10 (19) (20) GND + GPIO9 (21) (22) GPIO25 + GPIO11 (23) (24) GPIO8 + GND (25) (26) GPIO7 + GPIO0 (27) (28) GPIO1 + GPIO5 (29) (30) GND + GPIO6 (31) (32) GPIO12 + GPIO13 (33) (34) GND + GPIO19 (35) (36) GPIO16 + GPIO26 (37) (38) GPIO20 + GND (39) (40) GPIO21 + + For further information, please refer to https://pinout.xyz/ + +By default, if stdout is a console that supports color, ANSI codes will be used +to produce color output. Output can be forced to be :option:`--monochrome`: + +.. code-block:: console + + $ pinout --monochrome + +Or forced to be :option:`--color`, in case you are redirecting to something +capable of supporting ANSI codes: + +.. code-block:: console + + $ pinout --color | less -SR + +To manually specify the revision of Pi you want to query, use +:option:`--revision`. The tool understands both old-style `revision codes`_ +(such as for the model B): + +.. code-block:: console + + $ pinout -r 000d + +Or new-style `revision codes`_ (such as for the Pi Zero W): + +.. code-block:: console + + $ pinout -r 9000c1 + +.. image:: images/pinout_pizero_w.png + :align: center + :width: 537px + +You can also use the tool with :doc:`remote_gpio` to query remote Raspberry +Pi's: + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=other_pi pinout + +Or run the tool directly on a PC using the mock pin implementation (although in +this case you'll almost certainly want to specify the Pi revision manually): + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=mock pinout -r a22042 + + +.. only:: builder_man + + See Also + -------- + + :manpage:`pintest(1)`, :manpage:`remote-gpio(7)`, + :manpage:`gpiozero-env(7)` + +.. _pinout.xyz: https://pinout.xyz/ +.. _revision codes: https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes diff --git a/docs/cli_pintest.rst b/docs/cli_pintest.rst new file mode 100644 index 000000000..f8b539b92 --- /dev/null +++ b/docs/cli_pintest.rst @@ -0,0 +1,124 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2023 Dave Jones +.. +.. SPDX-License-Identifier: BSD-3-Clause + +pintest +======= + +A utility for testing the GPIO pins on a Raspberry Pi, inspired by pigpio's +gpiotest example script, and wiringPi's pintest utility. + +.. only:: not builder_man + + .. versionadded:: 2.0 + The :program:`pintest` utility. + + .. image:: images/pintest.png + :alt: A screenshot of the output from pintest. In a terminal window, + pintest has prompted the user with the list of GPIOs it intends + to test and asked for confirmation to proceed. Having received + this confirmation, it's printed out each GPIO in turn with "ok" + after it, indicating success. + + +Synopsis +-------- + +:: + + pintest [-h] [--version] [-p PINS] [-s SKIP] [-y] [-r REVISION] + + +Description +----------- + +A utility for testing the function of GPIOs on a Raspberry Pi. It is possible +to damage the GPIOs on a Pi by passing too much current (or voltage in the case +of inputs) through them. The :program:`pintest` utility can be used to +determine if any of the GPIOs on a Pi are broken. + +The utility will test all physically exposed GPIOs (those on the main GPIO +header) by default, but you may wish to only test a subset, or to exclude +certain GPIOs which can be accomplished with the :option:`pintest --pins` or +:option:`pintest --skip` options. + +.. note:: + + You must ensure that nothing is connected to the GPIOs that you intend to + test. By default, the utility will prompt you before proceeding, repeating + this warning. + +In the event that any GPIO is found to be faulty, it will be reported in the +output and the utility will exit with a return code of 1. If all specified +GPIOs test fine, the return code is zero. + + +Options +------- + +.. program:: pintest + +.. option:: -h, --help + + show this help message and exit + +.. option:: --version + + Show the program's version number and exit + +.. option:: -p PINS, --pins PINS + + The pin(s) to test. Can be specified as a comma-separated list of pins. Pin + numbers can be given in any form accepted by gpiozero, e.g. 14, GPIO14, + BOARD8. The default is to test all pins + +.. option:: -s SKIP, --skip SKIP + + The pin(s) to skip testing. Can be specified as comma-separated list of + pins. Pin numbers can be given in any form accepted by gpiozero, e.g. 14, + GPIO14, BOARD8. The default is to skip no pins + +.. option:: -y, --yes + + Proceed without prompting + +.. option:: -r REVISION, --revision REVISION + + Force board revision. Default is to autodetect revision of current device. + You should avoid this option unless you are very sure the detection is + incorrect + + +Examples +-------- + +Test all physically exposed GPIOs on the board: + +.. code-block:: console + + $ pintest + +Test just the I2C GPIOs without prompting: + +.. code-block:: console + + $ pintest --pins 2,3 --yes + +Exclude the SPI GPIOs from testing: + +.. code-block:: console + + $ pintest --exclude GPIO7,GPIO8,GPIO9,GPIO10,GPIO11 + +Note that pin numbers can be given in any form accepted by GPIO Zero, e.g. 14, +GPIO14, or BOARD8. + + +.. only:: builder_man + + See Also + -------- + + :manpage:`pinout(1)`, :manpage:`gpiozero-env(7)` diff --git a/docs/cli_tools.rst b/docs/cli_tools.rst new file mode 100644 index 000000000..bb01e37d1 --- /dev/null +++ b/docs/cli_tools.rst @@ -0,0 +1,24 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2021 Ben Nuttall +.. Copyright (c) 2016 Stewart +.. +.. SPDX-License-Identifier: BSD-3-Clause + +================== +Command-line Tools +================== + +The gpiozero package contains a database of information about the various +revisions of Raspberry Pi. This is queried by the :program:`pinout` +command-line tool to output details of the GPIO pins available. The +:program:`pintest` tool is also provided to test the operation of GPIO pins on +the board. + +.. toctree:: + :maxdepth: 1 + + cli_pinout + cli_pintest + cli_env diff --git a/docs/compat.rst b/docs/compat.rst new file mode 100644 index 000000000..9773941d1 --- /dev/null +++ b/docs/compat.rst @@ -0,0 +1,301 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2021-2023 Dave Jones +.. Copyright (c) 2023 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +======================= +Backwards Compatibility +======================= + + +.. currentmodule:: gpiozero + +GPIO Zero 2.x is a new major version and thus backwards incompatible changes +can be expected. We have attempted to keep these as minimal as reasonably +possible while taking advantage of the opportunity to clean up things. This +chapter documents breaking changes from version 1.x of the library to 2.x, and +all deprecated functionality which will still work in release 2.0 but is +scheduled for removal in a future 2.x release. + + +Finding and fixing deprecated usage +=================================== + +As of release 2.0, all deprecated functionality will raise +:exc:`DeprecationWarning` when used. By default, the Python interpreter +suppresses these warnings (as they're only of interest to developers, not +users) but you can easily configure different behaviour. + +The following example script uses a number of deprecated functions:: + + import gpiozero + + board = gpiozero.pi_info() + for header in board.headers.values(): + for pin in header.pins.values(): + if pin.pull_up: + print(pin.function, 'is pulled up') + +Despite using deprecated functionality the script runs happily (and silently) +with gpiozero 2.0. To discover what deprecated functions are being used, we add +a couple of lines to tell the warnings module that we want "default" handling +of :exc:`DeprecationWarning`; "default" handling means that the first time an +attempt is made to raise this warning at a particular location, the warning's +details will be printed to the console. All future invocations from the same +location will be ignored. This saves flooding the console with warning details +from tight loops. With this change, the script looks like this:: + + import gpiozero + + import warnings + warnings.filterwarnings('default', category=DeprecationWarning) + + board = gpiozero.pi_info() + for header in board.headers.values(): + for pin in header.pins.values(): + if pin.pull_up: + print(pin.function, 'is pulled up') + +And produces the following output on the console when run: + +.. code-block:: text + + /home/dave/projects/home/gpiozero/gpio-zero/gpiozero/pins/__init__.py:899: + DeprecationWarning: PinInfo.pull_up is deprecated; please use PinInfo.pull + warnings.warn( + /home/dave/projects/home/gpiozero/gpio-zero/gpiozero/pins/__init__.py:889: + DeprecationWarning: PinInfo.function is deprecated; please use PinInfo.name + warnings.warn( + GPIO2 is pulled up + GPIO3 is pulled up + +This tells us which pieces of deprecated functionality are being used in our +script, but it doesn't tell us where in the script they were used. For this, +it is more useful to have warnings converted into full blown exceptions. With +this change, each time a :exc:`DeprecationWarning` would have been printed, it +will instead cause the script to terminate with an unhandled exception and a +full stack trace:: + + import gpiozero + + import warnings + warnings.filterwarnings('error', category=DeprecationWarning) + + board = gpiozero.pi_info() + for header in board.headers.values(): + for pin in header.pins.values(): + if pin.pull_up: + print(pin.function, 'is pulled up') + +Now when we run the script it produces the following: + +.. code-block:: pycon + + Traceback (most recent call last): + File "/home/dave/projects/home/gpiozero/gpio-zero/foo.py", line 9, in + if pin.pull_up: + File "/home/dave/projects/home/gpiozero/gpio-zero/gpiozero/pins/__init__.py", line 899, in pull_up + warnings.warn( + DeprecationWarning: PinInfo.pull_up is deprecated; please use PinInfo.pull + +This tells us that line 9 of our script is using deprecated functionality, and +provides a hint of how to fix it. We change line 9 to use the "pull" attribute +instead. Now we run again, and this time get the following: + +.. code-block:: pycon + + Traceback (most recent call last): + File "/home/dave/projects/home/gpiozero/gpio-zero/foo.py", line 10, in + print(pin.function, 'is pulled up') + File "/home/dave/projects/home/gpiozero/gpio-zero/gpiozero/pins/__init__.py", line 889, in function + warnings.warn( + DeprecationWarning: PinInfo.function is deprecated; please use PinInfo.name + +Now we can tell line 10 has a problem, and once again the exception tells us +how to fix it. We continue in this fashion until the script looks like this:: + + import gpiozero + + import warnings + warnings.filterwarnings('error', category=DeprecationWarning) + + board = gpiozero.pi_info() + for header in board.headers.values(): + for pin in header.pins.values(): + if pin.pull == 'up': + print(pin.name, 'is pulled up') + +The script now runs to completion, so we can be confident it's no longer using +any deprecated functionality and will run happily even when this functionality +is removed in a future 2.x release. At this point, you may wish to remove the +``filterwarnings`` line as well (or at least comment it out). + + +Python 2.x support dropped +========================== + +By far the biggest and most important change is that the Python 2.x series is +no longer supported (in practice, this means Python 2.7 is no longer +supported). If your code is not compatible with Python 3, you should follow the +`porting guide`_ in the `Python documentation`_. + +As of GPIO Zero 2.0, the lowest supported Python version will be 3.5. This base +version may advance with minor releases, but we will make a reasonable best +effort not to break compatibility with old Python 3.x versions, and to ensure +that GPIO Zero can run on the version of Python in Debian oldstable at the +time of its release. + + +RPIO pin factory removed +======================== + +The RPIO pin implementation is unsupported on the Raspberry Pi 2 onwards and +hence of little practical use these days. Anybody still relying on RPIO's +stable PWM implementation is advised to try the pigpio pin implementation +instead (also supported by GPIO Zero). + + +Deprecated pin-factory aliases removed +====================================== + +Several deprecated aliases for pin factories, which could be specified by the +:envvar:`GPIOZERO_PIN_FACTORY` environment variable, have been removed: + +* "PiGPIOPin" is removed in favour of "pigpio" + +* "RPiGPIOPin" is removed in favour of "rpigpio" + +* "NativePin" is removed in favour of "native" + +In other words, you can no longer use the following when invoking your +script: + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=PiGPIOPin python3 my_script.py + +Instead, you should use the following: + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=pigpio python3 my_script.py + + +Keyword arguments +================= + +Many classes in GPIO Zero 1.x were documented as having keyword-only arguments +in their constructors and methods. For example, the :class:`PiLiter` was +documented as having the constructor: ``PiLiter(*, pwm=False, +initial_value=False, pin_factory=None)`` implying that all its arguments were +keyword only. + +However, despite being documented in this manner, this was rarely enforced as +it was extremely difficult to do so under Python 2.x without complicating the +code-base considerably (Python 2.x lacked the "*" syntax to declare +keyword-only arguments; they could only be implemented via "\*\*kwargs" +arguments and dictionary manipulation). + +In GPIO Zero 2.0, all such arguments are now *actually* keyword arguments. If +your code complied with the 1.x documentation you shouldn't notice any +difference. In other words, the following is still fine:: + + from gpiozero import PiLiter + + l = PiLiter(pwm=True) + +However, if you had omitted the keyword you will need to modify your code:: + + from gpiozero import PiLiter + + l = PiLiter(True) # this will no longer work + + +Robots take Motors, and PhaseEnableRobot is deprecated +====================================================== + +The GPIO Zero 1.x API specified that a :class:`Robot` was constructed with two +tuples that were in turn used to construct two :class:`Motor` instances. The +2.x API replaces this with simply passing in the :class:`Motor`, or +:func:`PhaseEnableMotor` instances you wish to use as the left and right +wheels. + +If your current code uses pins 4 and 14 for the left wheel, and 17 and 18 for +the right wheel, it may look like this:: + + from gpiozero import Robot + + r = Robot(left=(4, 14), right=(17, 18)) + +This should be changed to the following:: + + from gpiozero import Robot, Motor + + r = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +Likewise, if you are currently using :func:`PhaseEnableRobot` your code may +look like this:: + + from gpiozero import PhaseEnableRobot + + r = PhaseEnableRobot(left=(4, 14), right=(17, 18)) + +This should be changed to the following:: + + from gpiozero import Robot, PhaseEnableMotor + + r = Robot(left=PhaseEnableMotor(4, 14), + right=PhaseEnableMotor(17, 18)) + +This change came about because the :class:`Motor` class was also documented as +having two mandatory parameters (*forward* and *backward*) and several +keyword-only parameters, including the *enable* pin. However, *enable* was +treated as a positional argument for the sake of constructing :class:`Robot` +which was inconsistent. Furthermore, :func:`PhaseEnableRobot` was more or less +a redundant duplicate of :class:`Robot` but was lacking a couple of features +added to :class:`Robot` later (notable "curved" turning). + +Although the new API requires a little more typing, it does mean that phase +enable robot boards now inherit all the functionality of :class:`Robot` because +that's all they use. Theoretically you could also mix and match regular motors +and phase-enable motors although there's little sense in doing so. + +The former functionality (passing tuples to the :class:`Robot` constructor) +will remain as deprecated functionality for gpiozero 2.0, but will be removed +in a future 2.x release. :func:`PhaseEnableRobot` remains as a stub function +which simply returns a :class:`Robot` instance, but this will be removed in a +future 2.x release. + + +PiBoardInfo, HeaderInfo, PinInfo +================================ + +The :class:`PiBoardInfo` class, and the associated :class:`HeaderInfo` and +:class:`PinInfo` classes have undergone a major re-structuring. This is partly +because some of the prior terminology was confusing (e.g. the meaning of +:attr:`PinInfo.function` and :attr:`Pin.function` clashed), and partly because +with the addition of the "lgpio" factory it's entirely possible to use gpiozero +on non-Pi boards (although at present the :class:`pins.lgpio.LGPIOFactory` is +still written assuming it is only ever used on a Pi). + +As a result the following classes, methods, and attributes are deprecated +(not yet removed, but will be in a future release within the 2.x series): + +* :attr:`Factory.pi_info` is deprecated in favour of :attr:`Factory.board_info` + which returns a :class:`BoardInfo` instead of :class:`PiBoardInfo` (which is + now a subclass of the former). + +* :attr:`PinInfo.pull_up` is deprecated in favour of :attr:`PinInfo.pull`. + +* :attr:`PinInfo.function` is deprecated in favour of :attr:`PinInfo.name`. + +* :meth:`BoardInfo.physical_pins`, :meth:`BoardInfo.physical_pin`, and + :meth:`BoardInfo.pulled_up`, are all deprecated in favour of a combination of + the new :meth:`BoardInfo.find_pin` and the attributes mentioned above. + +* :attr:`PiPin.number` is deprecated in favour of :attr:`Pin.info.name`. + +.. _Python documentation: https://docs.python.org/3/ +.. _porting guide: https://docs.python.org/3/howto/pyporting.html diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..4c823b96d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2023 Dave Jones +# Copyright (c) 2016 Thijs Triemstra +# +# SPDX-License-Identifier: BSD-3-Clause + +import os +import configparser +from pathlib import Path +from datetime import datetime + +on_rtd = os.environ.get('READTHEDOCS', '').lower() == 'true' +config = configparser.ConfigParser() +config.read([Path(__file__).parent / '..' / 'setup.cfg']) +info = config['metadata'] + +# -- Project information ----------------------------------------------------- + +project = info['name'] +author = info['author'] +copyright = f'2015-{datetime.now():%Y} {author}' +release = info['version'] +version = release + +# -- General configuration ------------------------------------------------ + +needs_sphinx = '4.0' +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx.ext.imgmath', +] +if on_rtd: + tags.add('rtd') + +root_doc = 'index' +templates_path = ['_templates'] +exclude_patterns = ['_build'] +highlight_language = 'python3' +pygments_style = 'sphinx' + +# -- Autodoc configuration ------------------------------------------------ + +autodoc_member_order = 'groupwise' +autodoc_mock_imports = [ + 'RPi', + 'lgpio', + 'RPIO', + 'pigpio', + 'w1thermsensor', + 'spidev', + 'colorzero', +] + +# -- Intersphinx configuration -------------------------------------------- + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3.9', None), + 'picamera': ('https://picamera.readthedocs.io/en/latest', None), + 'colorzero': ('https://colorzero.readthedocs.io/en/latest', None), +} + +# -- Options for HTML output ---------------------------------------------- + +html_theme = 'sphinx_rtd_theme' +html_title = f'{project} {version} Documentation' +html_static_path = ['_static'] +manpages_url = 'https://manpages.debian.org/bookworm/{page}.{section}.en.html' + +# -- Options for LaTeX output --------------------------------------------- + +latex_engine = 'xelatex' + +latex_elements = { + 'papersize': 'a4paper', + 'pointsize': '10pt', + 'preamble': r'\def\thempfootnote{\arabic{mpfootnote}}', # workaround sphinx issue #2530 +} + +latex_documents = [ + ( + 'index', # source start file + project + '.tex', # target filename + html_title, # title + author, # author + 'manual', # documentclass + True, # documents ref'd from toctree only + ), +] + +latex_show_pagerefs = True +latex_show_urls = 'footnote' + +# -- Options for epub output ---------------------------------------------- + +epub_basename = project +epub_author = author +epub_identifier = f'https://{info["name"]}.readthedocs.io/' +epub_show_urls = 'no' + +# -- Options for manual page output --------------------------------------- + +man_pages = [ + ('cli_pinout', 'pinout', 'GPIO Zero pinout tool', [info['author']], 1), + ('cli_pintest', 'pintest', 'GPIO Zero pintest tool', [info['author']], 1), + ('remote_gpio', 'remote-gpio', 'GPIO Zero remote GPIO guide', [info['author']], 7), + ('cli_env', 'gpiozero-env', 'GPIO Zero environment vars', [info['author']], 7), +] + +man_show_urls = True + +# -- Options for linkcheck builder ---------------------------------------- + +linkcheck_retries = 3 +linkcheck_workers = 20 +linkcheck_anchors = True diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 000000000..c055c76e4 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,78 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2016-2023 Dave Jones +.. Copyright (c) 2016-2021 Ben Nuttall +.. Copyright (c) 2017 rgm +.. +.. SPDX-License-Identifier: BSD-3-Clause + +.. _contributing: + +============ +Contributing +============ + +Contributions to the library are welcome! Here are some guidelines to follow. + + +Suggestions +=========== + +Please make suggestions for additional components or enhancements to the +codebase by opening an `issue`_ explaining your reasoning clearly. + + +Bugs +==== + +Please submit bug reports by opening an `issue`_ explaining the problem clearly +using code examples. + + +Documentation +============= + +The documentation source lives in the `docs`_ folder. Contributions to the +documentation are welcome but should be easy to read and understand. + + +Commit messages and pull requests +================================= + +Commit messages should be concise but descriptive, and in the form of a patch +description, i.e. instructional not past tense ("Add LED example" not "Added +LED example"). + +Commits which close (or intend to close) an issue should include the phrase +"fix #123" or "close #123" where ``#123`` is the issue number, as well as +include a short description, for example: "Add LED example, close #123", and +pull requests should aim to match or closely match the corresponding issue +title. + +Copyrights on submissions are owned by their authors (we don't bother with +copyright assignments), and we assume that authors are happy for their code to +be released under the project's :doc:`license `. Do feel free to add +your name to the list of contributors in :file:`README.rst` at the top level of +the project in your pull request! Don't worry about adding your name to the +copyright headers in whatever files you touch; these are updated automatically +from the git metadata before each release. + + +Backwards compatibility +======================= + +Since this library reached v1.0 we aim to maintain backwards-compatibility +thereafter. Changes which break backwards-compatibility will not be accepted. + + +Python 2/3 +========== + +The library is 100% compatible with both Python 2.7 and Python 3 from version +3.2 onwards. Since Python 2 is now past its `end-of-life`_, the 1.6.2 release +(2021-03-18) is the last to support Python 2. + + +.. _docs: https://github.com/gpiozero/gpiozero/tree/master/docs +.. _issue: https://github.com/gpiozero/gpiozero/issues/new +.. _end-of-life: http://legacy.python.org/dev/peps/pep-0373/ diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 000000000..8ec26f20c --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,188 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2018-2019 Ben Nuttall +.. Copyright (c) 2018 Steveis +.. +.. SPDX-License-Identifier: BSD-3-Clause + +=========== +Development +=========== + +.. currentmodule:: gpiozero + +The main GitHub repository for the project can be found at: + + https://github.com/gpiozero/gpiozero + +For anybody wishing to hack on the project, we recommend starting off by +getting to grips with some simple device classes. Pick something like +:class:`LED` and follow its heritage backward to :class:`DigitalOutputDevice`. +Follow that back to :class:`OutputDevice` and you should have a good +understanding of simple output devices along with a grasp of how GPIO Zero +relies fairly heavily upon inheritance to refine the functionality of devices. +The same can be done for input devices, and eventually more complex devices +(composites and SPI based). + + +.. _dev_install: + +Development installation +======================== + +If you wish to develop GPIO Zero itself, we recommend obtaining the source by +cloning the GitHub repository and then use the "develop" target of the Makefile +which will install the package as a link to the cloned repository allowing +in-place development (it also builds a tags file for use with vim/emacs with +Exuberant’s ctags utility). The following example demonstrates this method +within a virtual Python environment: + +.. code-block:: console + + $ sudo apt install lsb-release build-essential git exuberant-ctags \ + virtualenvwrapper python3-virtualenv python3-dev + +After installing ``virtualenvwrapper`` you'll need to restart your shell before +commands like :command:`mkvirtualenv` will operate correctly. Once you've +restarted your shell, continue: + +.. code-block:: console + + $ cd + $ mkvirtualenv -p /usr/bin/python3 gpiozero + $ workon gpiozero + (gpiozero) $ git clone https://github.com/gpiozero/gpiozero.git + (gpiozero) $ cd gpiozero + (gpiozero) $ make develop + +You will likely wish to install one or more pin implementations within the +virtual environment (if you don't, GPIO Zero will use the "native" pin +implementation which is usable at this stage, but doesn't support facilities +like PWM): + +.. code-block:: console + + (gpiozero) $ pip install rpi.gpio pigpio + +If you are working on SPI devices you may also wish to install the ``spidev`` +package to provide hardware SPI capabilities (again, GPIO Zero will work +without this, but a big-banging software SPI implementation will be used +instead which limits bandwidth): + +.. code-block:: console + + (gpiozero) $ pip install spidev + +To pull the latest changes from git into your clone and update your +installation: + +.. code-block:: console + + $ workon gpiozero + (gpiozero) $ cd ~/gpiozero + (gpiozero) $ git pull + (gpiozero) $ make develop + +To remove your installation, destroy the sandbox and the clone: + +.. code-block:: console + + (gpiozero) $ deactivate + $ rmvirtualenv gpiozero + $ rm -rf ~/gpiozero + + +Building the docs +================= + +If you wish to build the docs, you'll need a few more dependencies. Inkscape +is used for conversion of SVGs to other formats, Graphviz is used for rendering +certain charts, and TeX Live is required for building PDF output. The following +command should install all required dependencies: + +.. code-block:: console + + $ sudo apt install texlive-latex-recommended texlive-latex-extra \ + texlive-fonts-recommended texlive-xetex graphviz inkscape \ + python3-sphinx python3-sphinx-rtd-theme latexmk xindy + +Once these are installed, you can use the "doc" target to build the +documentation: + +.. code-block:: console + + $ workon gpiozero + (gpiozero) $ cd ~/gpiozero + (gpiozero) $ make doc + +The HTML output is written to :file:`build/html` while the PDF output +goes to :file:`build/latex`. + + +Test suite +========== + +If you wish to run the GPIO Zero test suite, follow the instructions in +:ref:`dev_install` above and then make the "test" target within the sandbox. +You'll also need to install some pip packages: + +.. code-block:: console + + $ workon gpiozero + (gpiozero) $ pip install coverage mock pytest + (gpiozero) $ cd ~/gpiozero + (gpiozero) $ make test + +The test suite expects pins 22 and 27 (by default) to be wired together in +order to run the "real" pin tests. The pins used by the test suite can be +overridden with the environment variables :envvar:`GPIOZERO_TEST_PIN` (defaults +to 22) and :envvar:`GPIOZERO_TEST_INPUT_PIN` (defaults to 27). + +.. warning:: + + When wiring GPIOs together, ensure a load (like a 1KΩ resistor) is placed + between them. Failure to do so may lead to blown GPIO pins (your humble + author has a fried GPIO27 as a result of such laziness, although it did + take *many* runs of the test suite before this occurred!). + +The test suite is also setup for usage with the :command:`tox` utility, in +which case it will attempt to execute the test suite with all supported +versions of Python. If you are developing under Ubuntu you may wish to look +into the `Dead Snakes PPA`_ in order to install old/new versions of Python; the +tox setup *should* work with the version of tox shipped with Ubuntu Xenial, but +more features (like parallel test execution) are available with later versions. + +On the subject of parallel test execution, this is also supported in the tox +setup, including the "real" pin tests (a file-system level lock is used to +ensure different interpreters don't try to access the physical pins +simultaneously). + +For example, to execute the test suite under tox, skipping interpreter versions +which are not installed: + +.. code-block:: console + + $ tox -s + +To execute the test suite under all installed interpreter versions in parallel, +using as many parallel tasks as there are CPUs, then displaying a combined +report of coverage from all environments: + +.. code-block:: console + + $ tox -p auto -s + $ coverage combine --rcfile coverage.cfg + $ coverage report --rcfile coverage.cfg + + +.. _Dead Snakes PPA: https://launchpad.net/~deadsnakes/%2Barchive/ubuntu/ppa + + +Mock pins +========= + +The test suite largely depends on the existence of the mock pin factory +:class:`~gpiozero.pins.mock.MockFactory`, which is also useful for manual +testing, for example in the Python shell or another REPL. See the section on +:ref:`mock-pins` in the :doc:`api_pins` chapter for more information. diff --git a/docs/examples/all_on_1.py b/docs/examples/all_on_1.py new file mode 100644 index 000000000..978e4e147 --- /dev/null +++ b/docs/examples/all_on_1.py @@ -0,0 +1,9 @@ +from gpiozero import FishDish +from signal import pause + +fish = FishDish() + +fish.button.when_pressed = fish.on +fish.button.when_released = fish.off + +pause() diff --git a/docs/examples/all_on_2.py b/docs/examples/all_on_2.py new file mode 100644 index 000000000..0dccc78aa --- /dev/null +++ b/docs/examples/all_on_2.py @@ -0,0 +1,9 @@ +from gpiozero import TrafficHat +from signal import pause + +th = TrafficHat() + +th.button.when_pressed = th.on +th.button.when_released = th.off + +pause() diff --git a/docs/examples/all_on_3.py b/docs/examples/all_on_3.py new file mode 100644 index 000000000..682adb40f --- /dev/null +++ b/docs/examples/all_on_3.py @@ -0,0 +1,23 @@ +from gpiozero import LED, Buzzer, Button +from signal import pause + +button = Button(2) +buzzer = Buzzer(3) +red = LED(4) +amber = LED(5) +green = LED(6) + +things = [red, amber, green, buzzer] + +def things_on(): + for thing in things: + thing.on() + +def things_off(): + for thing in things: + thing.off() + +button.when_pressed = things_on +button.when_released = things_off + +pause() diff --git a/docs/examples/angular_servo.py b/docs/examples/angular_servo.py new file mode 100644 index 000000000..5ab1b1de3 --- /dev/null +++ b/docs/examples/angular_servo.py @@ -0,0 +1,16 @@ +from gpiozero import AngularServo +from time import sleep + +servo = AngularServo(17, min_angle=-90, max_angle=90) + +while True: + servo.angle = -90 + sleep(2) + servo.angle = -45 + sleep(2) + servo.angle = 0 + sleep(2) + servo.angle = 45 + sleep(2) + servo.angle = 90 + sleep(2) diff --git a/docs/examples/bluedot_led.py b/docs/examples/bluedot_led.py new file mode 100644 index 000000000..305e4def9 --- /dev/null +++ b/docs/examples/bluedot_led.py @@ -0,0 +1,11 @@ +from bluedot import BlueDot +from gpiozero import LED + +bd = BlueDot() +led = LED(17) + +while True: + bd.wait_for_press() + led.on() + bd.wait_for_release() + led.off() diff --git a/docs/examples/bluedot_robot_1.py b/docs/examples/bluedot_robot_1.py new file mode 100644 index 000000000..bc02eb1b4 --- /dev/null +++ b/docs/examples/bluedot_robot_1.py @@ -0,0 +1,22 @@ +from bluedot import BlueDot +from gpiozero import Robot, Motor +from signal import pause + +bd = BlueDot() +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +def move(pos): + if pos.top: + robot.forward(pos.distance) + elif pos.bottom: + robot.backward(pos.distance) + elif pos.left: + robot.left(pos.distance) + elif pos.right: + robot.right(pos.distance) + +bd.when_pressed = move +bd.when_moved = move +bd.when_released = robot.stop + +pause() diff --git a/docs/examples/bluedot_robot_2.py b/docs/examples/bluedot_robot_2.py new file mode 100644 index 000000000..c55f6c754 --- /dev/null +++ b/docs/examples/bluedot_robot_2.py @@ -0,0 +1,26 @@ +from gpiozero import Robot, Motor +from bluedot import BlueDot +from signal import pause + +def pos_to_values(x, y): + left = y if x > 0 else y + x + right = y if x < 0 else y - x + return (clamped(left), clamped(right)) + +def clamped(v): + return max(-1, min(1, v)) + +def drive(): + while True: + if bd.is_pressed: + x, y = bd.position.x, bd.position.y + yield pos_to_values(x, y) + else: + yield (0, 0) + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) +bd = BlueDot() + +robot.source = drive() + +pause() diff --git a/docs/examples/button_1.py b/docs/examples/button_1.py new file mode 100644 index 000000000..5ea7e495a --- /dev/null +++ b/docs/examples/button_1.py @@ -0,0 +1,9 @@ +from gpiozero import Button + +button = Button(2) + +while True: + if button.is_pressed: + print("Button is pressed") + else: + print("Button is not pressed") diff --git a/docs/examples/button_2.py b/docs/examples/button_2.py new file mode 100644 index 000000000..406762733 --- /dev/null +++ b/docs/examples/button_2.py @@ -0,0 +1,6 @@ +from gpiozero import Button + +button = Button(2) + +button.wait_for_press() +print("Button was pressed") diff --git a/docs/examples/button_3.py b/docs/examples/button_3.py new file mode 100644 index 000000000..55694e1a5 --- /dev/null +++ b/docs/examples/button_3.py @@ -0,0 +1,11 @@ +from gpiozero import Button +from signal import pause + +def say_hello(): + print("Hello!") + +button = Button(2) + +button.when_pressed = say_hello + +pause() diff --git a/docs/examples/button_4.py b/docs/examples/button_4.py new file mode 100644 index 000000000..d3f9d3469 --- /dev/null +++ b/docs/examples/button_4.py @@ -0,0 +1,15 @@ +from gpiozero import Button +from signal import pause + +def say_hello(): + print("Hello!") + +def say_goodbye(): + print("Goodbye!") + +button = Button(2) + +button.when_pressed = say_hello +button.when_released = say_goodbye + +pause() diff --git a/docs/examples/button_camera_1.py b/docs/examples/button_camera_1.py new file mode 100644 index 000000000..a7bf9535c --- /dev/null +++ b/docs/examples/button_camera_1.py @@ -0,0 +1,14 @@ +from gpiozero import Button +from picamera import PiCamera +from datetime import datetime +from signal import pause + +button = Button(2) +camera = PiCamera() + +def capture(): + camera.capture(f'/home/pi/{datetime.now():%Y-%m-%d-%H-%M-%S}.jpg') + +button.when_pressed = capture + +pause() diff --git a/docs/examples/button_camera_2.py b/docs/examples/button_camera_2.py new file mode 100644 index 000000000..fa1f70d35 --- /dev/null +++ b/docs/examples/button_camera_2.py @@ -0,0 +1,17 @@ +from gpiozero import Button +from picamera import PiCamera +from datetime import datetime +from signal import pause + +left_button = Button(2) +right_button = Button(3) +camera = PiCamera() + +def capture(): + camera.capture(f'/home/pi/{datetime.now():%Y-%m-%d-%H-%M-%S}.jpg') + +left_button.when_pressed = camera.start_preview +left_button.when_released = camera.stop_preview +right_button.when_pressed = capture + +pause() diff --git a/docs/examples/button_led_1.py b/docs/examples/button_led_1.py new file mode 100644 index 000000000..3dce993b1 --- /dev/null +++ b/docs/examples/button_led_1.py @@ -0,0 +1,10 @@ +from gpiozero import LED, Button +from signal import pause + +led = LED(17) +button = Button(2) + +button.when_pressed = led.on +button.when_released = led.off + +pause() diff --git a/docs/examples/button_led_2.py b/docs/examples/button_led_2.py new file mode 100644 index 000000000..d7bb55915 --- /dev/null +++ b/docs/examples/button_led_2.py @@ -0,0 +1,9 @@ +from gpiozero import LED, Button +from signal import pause + +led = LED(17) +button = Button(2) + +led.source = button + +pause() diff --git a/docs/examples/button_shutdown.py b/docs/examples/button_shutdown.py new file mode 100644 index 000000000..2a91a409f --- /dev/null +++ b/docs/examples/button_shutdown.py @@ -0,0 +1,11 @@ +from gpiozero import Button +from subprocess import check_call +from signal import pause + +def shutdown(): + check_call(['sudo', 'poweroff']) + +shutdown_btn = Button(17, hold_time=2) +shutdown_btn.when_held = shutdown + +pause() diff --git a/docs/examples/button_stop_motion.py b/docs/examples/button_stop_motion.py new file mode 100644 index 000000000..2d738a4f7 --- /dev/null +++ b/docs/examples/button_stop_motion.py @@ -0,0 +1,12 @@ +from gpiozero import Button +from picamera import PiCamera + +button = Button(2) +camera = PiCamera() + +camera.start_preview() +frame = 1 +while True: + button.wait_for_press() + camera.capture(f'/home/pi/frame{frame:03d}.jpg') + frame += 1 diff --git a/docs/examples/color_picker.py b/docs/examples/color_picker.py new file mode 100644 index 000000000..2d9748da5 --- /dev/null +++ b/docs/examples/color_picker.py @@ -0,0 +1,30 @@ +from threading import Event +from colorzero import Color +from gpiozero import RotaryEncoder, RGBLED, Button + +rotor = RotaryEncoder(16, 20, wrap=True, max_steps=180) +rotor.steps = -180 +led = RGBLED(22, 23, 24, active_high=False) +btn = Button(21, pull_up=False) +led.color = Color('#f00') +done = Event() + +def change_hue(): + # Scale the rotor steps (-180..180) to 0..1 + hue = (rotor.steps + 180) / 360 + led.color = Color(h=hue, s=1, v=1) + +def show_color(): + print(f'Hue {led.color.hue.deg:.1f}° = {led.color.html}') + +def stop_script(): + print('Exiting') + done.set() + +print('Select a color by turning the knob') +rotor.when_rotated = change_hue +print('Push the button to see the HTML code for the color') +btn.when_released = show_color +print('Hold the button to exit') +btn.when_held = stop_script +done.wait() diff --git a/docs/examples/combining_sources.py b/docs/examples/combining_sources.py new file mode 100644 index 000000000..2fc42cf33 --- /dev/null +++ b/docs/examples/combining_sources.py @@ -0,0 +1,11 @@ +from gpiozero import Button, LED +from gpiozero.tools import all_values +from signal import pause + +button_a = Button(2) +button_b = Button(3) +led = LED(17) + +led.source = all_values(button_a, button_b) + +pause() diff --git a/docs/examples/cpu_temperature_bar_graph.py b/docs/examples/cpu_temperature_bar_graph.py new file mode 100644 index 000000000..8e1811007 --- /dev/null +++ b/docs/examples/cpu_temperature_bar_graph.py @@ -0,0 +1,9 @@ +from gpiozero import LEDBarGraph, CPUTemperature +from signal import pause + +cpu = CPUTemperature(min_temp=50, max_temp=90) +leds = LEDBarGraph(2, 3, 4, 5, 6, 7, 8, pwm=True) + +leds.source = cpu + +pause() diff --git a/docs/examples/custom_generator.py b/docs/examples/custom_generator.py new file mode 100644 index 000000000..19a0f27ab --- /dev/null +++ b/docs/examples/custom_generator.py @@ -0,0 +1,12 @@ +from gpiozero import LED +from random import randint +from signal import pause + +def rand(): + while True: + yield randint(0, 1) + +led = LED(17) +led.source = rand() + +pause() diff --git a/docs/examples/custom_generator_finite.py b/docs/examples/custom_generator_finite.py new file mode 100644 index 000000000..92aa34e17 --- /dev/null +++ b/docs/examples/custom_generator_finite.py @@ -0,0 +1,8 @@ +from gpiozero import LED +from signal import pause + +led = LED(17) +led.source_delay = 1 +led.source = [1, 0, 1, 1, 1, 0, 0, 1, 0, 1] + +pause() diff --git a/docs/examples/disk_usage_bar_graph.py b/docs/examples/disk_usage_bar_graph.py new file mode 100644 index 000000000..938e4bb0b --- /dev/null +++ b/docs/examples/disk_usage_bar_graph.py @@ -0,0 +1,9 @@ +from gpiozero import DiskUsage, LEDBarGraph +from signal import pause + +disk = DiskUsage() +graph = LEDBarGraph(2, 3, 4, 5, 6, 7, 8) + +graph.source = disk + +pause() diff --git a/docs/examples/distance_sensor_1.py b/docs/examples/distance_sensor_1.py new file mode 100644 index 000000000..c777aa9cf --- /dev/null +++ b/docs/examples/distance_sensor_1.py @@ -0,0 +1,8 @@ +from gpiozero import DistanceSensor +from time import sleep + +sensor = DistanceSensor(23, 24) + +while True: + print('Distance to nearest object is', sensor.distance, 'm') + sleep(1) diff --git a/docs/examples/distance_sensor_2.py b/docs/examples/distance_sensor_2.py new file mode 100644 index 000000000..86dfe74e5 --- /dev/null +++ b/docs/examples/distance_sensor_2.py @@ -0,0 +1,10 @@ +from gpiozero import DistanceSensor, LED +from signal import pause + +sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2) +led = LED(16) + +sensor.when_in_range = led.on +sensor.when_out_of_range = led.off + +pause() diff --git a/docs/examples/garden_light.py b/docs/examples/garden_light.py new file mode 100644 index 000000000..789f4bc7a --- /dev/null +++ b/docs/examples/garden_light.py @@ -0,0 +1,11 @@ +from gpiozero import LED, MotionSensor, LightSensor +from gpiozero.tools import booleanized, all_values +from signal import pause + +garden = LED(2) +motion = MotionSensor(4) +light = LightSensor(5) + +garden.source = all_values(booleanized(light, 0, 0.1), motion) + +pause() diff --git a/docs/examples/internet_status_indicator.py b/docs/examples/internet_status_indicator.py new file mode 100644 index 000000000..608dcf7d5 --- /dev/null +++ b/docs/examples/internet_status_indicator.py @@ -0,0 +1,14 @@ +from gpiozero import LED, PingServer +from gpiozero.tools import negated +from signal import pause + +green = LED(17) +red = LED(18) + +google = PingServer('google.com') + +google.when_activated = green.on +google.when_deactivated = green.off +red.source = negated(green) + +pause() diff --git a/docs/examples/led_1.py b/docs/examples/led_1.py new file mode 100644 index 000000000..5e031b993 --- /dev/null +++ b/docs/examples/led_1.py @@ -0,0 +1,10 @@ +from gpiozero import LED +from time import sleep + +red = LED(17) + +while True: + red.on() + sleep(1) + red.off() + sleep(1) diff --git a/docs/examples/led_2.py b/docs/examples/led_2.py new file mode 100644 index 000000000..c09e5dd5e --- /dev/null +++ b/docs/examples/led_2.py @@ -0,0 +1,8 @@ +from gpiozero import LED +from signal import pause + +red = LED(17) + +red.blink() + +pause() diff --git a/docs/examples/led_bargraph_1.py b/docs/examples/led_bargraph_1.py new file mode 100644 index 000000000..96534b6a6 --- /dev/null +++ b/docs/examples/led_bargraph_1.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBarGraph +from time import sleep + +graph = LEDBarGraph(5, 6, 13, 19, 26, 20) + +graph.value = 1 # (1, 1, 1, 1, 1, 1) +sleep(1) +graph.value = 1/2 # (1, 1, 1, 0, 0, 0) +sleep(1) +graph.value = -1/2 # (0, 0, 0, 1, 1, 1) +sleep(1) +graph.value = 1/4 # (1, 0, 0, 0, 0, 0) +sleep(1) +graph.value = -1 # (1, 1, 1, 1, 1, 1) +sleep(1) diff --git a/docs/examples/led_bargraph_2.py b/docs/examples/led_bargraph_2.py new file mode 100644 index 000000000..954499064 --- /dev/null +++ b/docs/examples/led_bargraph_2.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBarGraph +from time import sleep + +graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) + +graph.value = 1/10 # (0.5, 0, 0, 0, 0) +sleep(1) +graph.value = 3/10 # (1, 0.5, 0, 0, 0) +sleep(1) +graph.value = -3/10 # (0, 0, 0, 0.5, 1) +sleep(1) +graph.value = 9/10 # (1, 1, 1, 1, 0.5) +sleep(1) +graph.value = 95/100 # (1, 1, 1, 1, 0.75) +sleep(1) diff --git a/docs/examples/led_board_1.py b/docs/examples/led_board_1.py new file mode 100644 index 000000000..e4a3a684b --- /dev/null +++ b/docs/examples/led_board_1.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBoard +from time import sleep +from signal import pause + +leds = LEDBoard(5, 6, 13, 19, 26) + +leds.on() +sleep(1) +leds.off() +sleep(1) +leds.value = (1, 0, 1, 0, 1) +sleep(1) +leds.blink() + +pause() diff --git a/docs/examples/led_board_2.py b/docs/examples/led_board_2.py new file mode 100644 index 000000000..aa7efbcc6 --- /dev/null +++ b/docs/examples/led_board_2.py @@ -0,0 +1,8 @@ +from gpiozero import LEDBoard +from signal import pause + +leds = LEDBoard(5, 6, 13, 19, 26, pwm=True) + +leds.value = (0.2, 0.4, 0.6, 0.8, 1.0) + +pause() diff --git a/docs/examples/led_board_3.py b/docs/examples/led_board_3.py new file mode 100644 index 000000000..0bb4f8abe --- /dev/null +++ b/docs/examples/led_board_3.py @@ -0,0 +1,9 @@ +from gpiozero import LEDBoard +from time import sleep + +leds = LEDBoard(5, 6, 13, 19, 26) + +for led in leds: + led.on() + sleep(1) + led.off() diff --git a/docs/examples/led_board_4.py b/docs/examples/led_board_4.py new file mode 100644 index 000000000..62b9b6bdc --- /dev/null +++ b/docs/examples/led_board_4.py @@ -0,0 +1,11 @@ +from gpiozero import LEDBoard +from time import sleep + +leds = LEDBoard(2, 3, 4, 5, 6, 7, 8, 9) + +leds[0].on() # first led on +sleep(1) +leds[7].on() # last led on +sleep(1) +leds[-1].off() # last led off +sleep(1) diff --git a/docs/examples/led_board_5.py b/docs/examples/led_board_5.py new file mode 100644 index 000000000..f329f3908 --- /dev/null +++ b/docs/examples/led_board_5.py @@ -0,0 +1,24 @@ +from gpiozero import LEDBoard +from time import sleep + +leds = LEDBoard(2, 3, 4, 5, 6, 7, 8, 9) + +for led in leds[3:]: # leds 3 and onward + led.on() +sleep(1) +leds.off() + +for led in leds[:2]: # leds 0 and 1 + led.on() +sleep(1) +leds.off() + +for led in leds[::2]: # even leds (0, 2, 4...) + led.on() +sleep(1) +leds.off() + +for led in leds[1::2]: # odd leds (1, 3, 5...) + led.on() +sleep(1) +leds.off() diff --git a/docs/examples/led_board_6.py b/docs/examples/led_board_6.py new file mode 100644 index 000000000..8710cb226 --- /dev/null +++ b/docs/examples/led_board_6.py @@ -0,0 +1,11 @@ +from gpiozero import LEDBoard +from time import sleep + +leds = LEDBoard(red=2, green=3, blue=4) + +leds.red.on() +sleep(1) +leds.green.on() +sleep(1) +leds.blue.on() +sleep(1) diff --git a/docs/examples/led_board_7.py b/docs/examples/led_board_7.py new file mode 100644 index 000000000..5bfefe88a --- /dev/null +++ b/docs/examples/led_board_7.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBoard +from time import sleep + +leds = LEDBoard(red=LEDBoard(top=2, bottom=3), green=LEDBoard(top=4, bottom=5)) + +leds.red.on() ## both reds on +sleep(1) +leds.green.on() # both greens on +sleep(1) +leds.off() # all off +sleep(1) +leds.red.top.on() # top red on +sleep(1) +leds.green.bottom.on() # bottom green on +sleep(1) diff --git a/docs/examples/led_builtin.py b/docs/examples/led_builtin.py new file mode 100644 index 000000000..80a06ff68 --- /dev/null +++ b/docs/examples/led_builtin.py @@ -0,0 +1,9 @@ +from gpiozero import LED +from signal import pause + +power = LED(35) # /sys/class/leds/led1 +activity = LED(47) # /sys/class/leds/led0 + +activity.blink() +power.blink() +pause() diff --git a/docs/examples/led_button.py b/docs/examples/led_button.py new file mode 100644 index 000000000..d7bb55915 --- /dev/null +++ b/docs/examples/led_button.py @@ -0,0 +1,9 @@ +from gpiozero import LED, Button +from signal import pause + +led = LED(17) +button = Button(2) + +led.source = button + +pause() diff --git a/docs/examples/led_button_loop.py b/docs/examples/led_button_loop.py new file mode 100644 index 000000000..0b01872a0 --- /dev/null +++ b/docs/examples/led_button_loop.py @@ -0,0 +1,9 @@ +from gpiozero import LED, Button +from time import sleep + +led = LED(17) +button = Button(2) + +while True: + led.value = button.value + sleep(0.01) diff --git a/docs/examples/led_button_remote_1.py b/docs/examples/led_button_remote_1.py new file mode 100644 index 000000000..6563f2776 --- /dev/null +++ b/docs/examples/led_button_remote_1.py @@ -0,0 +1,12 @@ +from gpiozero import Button, LED +from gpiozero.pins.pigpio import PiGPIOFactory +from signal import pause + +factory = PiGPIOFactory(host='192.168.1.3') + +button = Button(2) +led = LED(17, pin_factory=factory) + +led.source = button + +pause() diff --git a/docs/examples/led_button_remote_2.py b/docs/examples/led_button_remote_2.py new file mode 100644 index 000000000..58e92c6ef --- /dev/null +++ b/docs/examples/led_button_remote_2.py @@ -0,0 +1,15 @@ +from gpiozero import Button, LED +from gpiozero.pins.pigpio import PiGPIOFactory +from gpiozero.tools import all_values +from signal import pause + +factory3 = PiGPIOFactory(host='192.168.1.3') +factory4 = PiGPIOFactory(host='192.168.1.4') + +led = LED(17) +button_1 = Button(17, pin_factory=factory3) +button_2 = Button(17, pin_factory=factory4) + +led.source = all_values(button_1, button_2) + +pause() diff --git a/docs/examples/led_char_display.py b/docs/examples/led_char_display.py new file mode 100644 index 000000000..c07ab673a --- /dev/null +++ b/docs/examples/led_char_display.py @@ -0,0 +1,10 @@ +from gpiozero import LEDCharDisplay +from time import sleep + +display = LEDCharDisplay(21, 20, 16, 22, 23, 24, 12, dp=25) + +for char in '321GO': + display.value = char + sleep(1) + +display.off() diff --git a/docs/examples/led_char_source.py b/docs/examples/led_char_source.py new file mode 100644 index 000000000..254854fd0 --- /dev/null +++ b/docs/examples/led_char_source.py @@ -0,0 +1,8 @@ +from gpiozero import LEDCharDisplay +from signal import pause + +display = LEDCharDisplay(21, 20, 16, 22, 23, 24, 12, dp=25) +display.source_delay = 1 +display.source = '321GO ' + +pause() diff --git a/docs/examples/led_pulse.py b/docs/examples/led_pulse.py new file mode 100644 index 000000000..bc6ac49d7 --- /dev/null +++ b/docs/examples/led_pulse.py @@ -0,0 +1,8 @@ +from gpiozero import PWMLED +from signal import pause + +led = PWMLED(17) + +led.pulse() + +pause() diff --git a/docs/examples/led_remote_1.py b/docs/examples/led_remote_1.py new file mode 100644 index 000000000..68d2135a2 --- /dev/null +++ b/docs/examples/led_remote_1.py @@ -0,0 +1,12 @@ +from gpiozero import LED +from gpiozero.pins.pigpio import PiGPIOFactory +from time import sleep + +factory = PiGPIOFactory(host='192.168.1.3') +led = LED(17, pin_factory=factory) + +while True: + led.on() + sleep(1) + led.off() + sleep(1) diff --git a/docs/examples/led_remote_2.py b/docs/examples/led_remote_2.py new file mode 100644 index 000000000..387646c39 --- /dev/null +++ b/docs/examples/led_remote_2.py @@ -0,0 +1,16 @@ +from gpiozero import LED +from gpiozero.pins.pigpio import PiGPIOFactory +from time import sleep + +factory3 = PiGPIOFactory(host='192.168.1.3') +factory4 = PiGPIOFactory(host='192.168.1.4') +led_1 = LED(17, pin_factory=factory3) +led_2 = LED(17, pin_factory=factory4) + +while True: + led_1.on() + led_2.off() + sleep(1) + led_1.off() + led_2.on() + sleep(1) diff --git a/docs/examples/led_remote_3.py b/docs/examples/led_remote_3.py new file mode 100644 index 000000000..9e4c49c0c --- /dev/null +++ b/docs/examples/led_remote_3.py @@ -0,0 +1,15 @@ +from gpiozero import LED +from gpiozero.pins.pigpio import PiGPIOFactory +from time import sleep + +remote_factory = PiGPIOFactory(host='192.168.1.3') +led_1 = LED(17) # local pin +led_2 = LED(17, pin_factory=remote_factory) # remote pin + +while True: + led_1.on() + led_2.off() + sleep(1) + led_1.off() + led_2.on() + sleep(1) diff --git a/docs/examples/led_remote_4.py b/docs/examples/led_remote_4.py new file mode 100644 index 000000000..a9396f7a5 --- /dev/null +++ b/docs/examples/led_remote_4.py @@ -0,0 +1,15 @@ +from gpiozero import LED +from gpiozero.pins.rpigpio import RPiGPIOFactory +from time import sleep + +local_factory = RPiGPIOFactory() +led_1 = LED(17, pin_factory=local_factory) # local pin +led_2 = LED(17) # remote pin + +while True: + led_1.on() + led_2.off() + sleep(1) + led_1.off() + led_2.on() + sleep(1) diff --git a/docs/examples/led_remote_5.py b/docs/examples/led_remote_5.py new file mode 100644 index 000000000..f498db4a2 --- /dev/null +++ b/docs/examples/led_remote_5.py @@ -0,0 +1,20 @@ +from gpiozero import LED +from gpiozero.pins.pigpio import PiGPIOFactory +from time import sleep + +factory3 = PiGPIOFactory(host='192.168.1.3') +factory4 = PiGPIOFactory(host='192.168.1.4') + +led_1 = LED(17) # local pin +led_2 = LED(17, pin_factory=factory3) # remote pin on one pi +led_3 = LED(17, pin_factory=factory4) # remote pin on another pi + +while True: + led_1.on() + led_2.off() + led_3.on() + sleep(1) + led_1.off() + led_2.on() + led_3.off() + sleep(1) diff --git a/docs/examples/led_travis.py b/docs/examples/led_travis.py new file mode 100644 index 000000000..fc292d887 --- /dev/null +++ b/docs/examples/led_travis.py @@ -0,0 +1,20 @@ +from travispy import TravisPy +from gpiozero import LED +from gpiozero.tools import negated +from time import sleep +from signal import pause + +def build_passed(repo): + t = TravisPy() + r = t.repo(repo) + while True: + yield r.last_build_state == 'passed' + +red = LED(12) +green = LED(16) + +green.source = build_passed('gpiozero/gpiozero') +green.source_delay = 60 * 5 # check every 5 minutes +red.source = negated(green) + +pause() diff --git a/docs/examples/led_variable_brightness.py b/docs/examples/led_variable_brightness.py new file mode 100644 index 000000000..4b9c2326e --- /dev/null +++ b/docs/examples/led_variable_brightness.py @@ -0,0 +1,12 @@ +from gpiozero import PWMLED +from time import sleep + +led = PWMLED(17) + +while True: + led.value = 0 # off + sleep(1) + led.value = 0.5 # half brightness + sleep(1) + led.value = 1 # full brightness + sleep(1) diff --git a/docs/examples/light_sensor_1.py b/docs/examples/light_sensor_1.py new file mode 100644 index 000000000..48ed9456e --- /dev/null +++ b/docs/examples/light_sensor_1.py @@ -0,0 +1,9 @@ +from gpiozero import LightSensor + +sensor = LightSensor(18) + +while True: + sensor.wait_for_light() + print("It's light! :)") + sensor.wait_for_dark() + print("It's dark :(") diff --git a/docs/examples/light_sensor_2.py b/docs/examples/light_sensor_2.py new file mode 100644 index 000000000..b618365e3 --- /dev/null +++ b/docs/examples/light_sensor_2.py @@ -0,0 +1,10 @@ +from gpiozero import LightSensor, LED +from signal import pause + +sensor = LightSensor(18) +led = LED(16) + +sensor.when_dark = led.on +sensor.when_light = led.off + +pause() diff --git a/docs/examples/light_sensor_3.py b/docs/examples/light_sensor_3.py new file mode 100644 index 000000000..ec60a1f18 --- /dev/null +++ b/docs/examples/light_sensor_3.py @@ -0,0 +1,9 @@ +from gpiozero import LightSensor, PWMLED +from signal import pause + +sensor = LightSensor(18) +led = PWMLED(16) + +led.source = sensor + +pause() diff --git a/docs/examples/matching_leds.py b/docs/examples/matching_leds.py new file mode 100644 index 000000000..2ea7e2b28 --- /dev/null +++ b/docs/examples/matching_leds.py @@ -0,0 +1,11 @@ +from gpiozero import LED, Button +from signal import pause + +red = LED(14) +green = LED(15) +button = Button(17) + +red.source = button +green.source = red + +pause() diff --git a/docs/examples/motion_sensor.py b/docs/examples/motion_sensor.py new file mode 100644 index 000000000..0ff122a7e --- /dev/null +++ b/docs/examples/motion_sensor.py @@ -0,0 +1,10 @@ +from gpiozero import MotionSensor, LED +from signal import pause + +pir = MotionSensor(4) +led = LED(16) + +pir.when_motion = led.on +pir.when_no_motion = led.off + +pause() diff --git a/docs/examples/motor.py b/docs/examples/motor.py new file mode 100644 index 000000000..a0d6fd324 --- /dev/null +++ b/docs/examples/motor.py @@ -0,0 +1,10 @@ +from gpiozero import Motor +from time import sleep + +motor = Motor(forward=4, backward=14) + +while True: + motor.forward() + sleep(5) + motor.backward() + sleep(5) diff --git a/docs/examples/multi_room_doorbell.py b/docs/examples/multi_room_doorbell.py new file mode 100644 index 000000000..33a11696c --- /dev/null +++ b/docs/examples/multi_room_doorbell.py @@ -0,0 +1,15 @@ +from gpiozero import Button, Buzzer +from gpiozero.pins.pigpio import PiGPIOFactory +from signal import pause + +ips = ['192.168.1.3', '192.168.1.4', '192.168.1.5', '192.168.1.6'] +remotes = [PiGPIOFactory(host=ip) for ip in ips] + +pin = 17 +button = Button(pin) # button on this pi +buzzers = [Buzzer(pin, pin_factory=r) for r in remotes] # buzzers on remote pins + +for buzzer in buzzers: + buzzer.source = button + +pause() diff --git a/docs/examples/multi_room_motion_alert.py b/docs/examples/multi_room_motion_alert.py new file mode 100644 index 000000000..7104429b8 --- /dev/null +++ b/docs/examples/multi_room_motion_alert.py @@ -0,0 +1,14 @@ +from gpiozero import LEDBoard, MotionSensor +from gpiozero.pins.pigpio import PiGPIOFactory +from gpiozero.tools import zip_values +from signal import pause + +ips = ['192.168.1.3', '192.168.1.4', '192.168.1.5', '192.168.1.6'] +remotes = [PiGPIOFactory(host=ip) for ip in ips] + +leds = LEDBoard(2, 3, 4, 5) # leds on this pi +sensors = [MotionSensor(17, pin_factory=r) for r in remotes] # remote sensors + +leds.source = zip_values(*sensors) + +pause() diff --git a/docs/examples/multichar_scroll.py b/docs/examples/multichar_scroll.py new file mode 100644 index 000000000..dd0d32d44 --- /dev/null +++ b/docs/examples/multichar_scroll.py @@ -0,0 +1,18 @@ +from itertools import cycle +from collections import deque +from gpiozero import LEDMultiCharDisplay +from signal import pause + +display = LEDMultiCharDisplay( + LEDCharDisplay(22, 23, 24, 25, 21, 20, 16, dp=12), 26, 19, 13, 6) + +def scroller(message, chars=4): + d = deque(maxlen=chars) + for c in cycle(message): + d.append(c) + if len(d) == chars: + yield ''.join(d) + +display.source_delay = 0.2 +display.source = scroller('GPIO 2ER0 ') +pause() diff --git a/docs/examples/music_box.py b/docs/examples/music_box.py new file mode 100644 index 000000000..80ac929ef --- /dev/null +++ b/docs/examples/music_box.py @@ -0,0 +1,16 @@ +from gpiozero import Button +import pygame.mixer +from pygame.mixer import Sound +from signal import pause + +pygame.mixer.init() + +button_sounds = { + Button(2): Sound("samples/drum_tom_mid_hard.wav"), + Button(3): Sound("samples/drum_cymbal_open.wav"), +} + +for button, sound in button_sounds.items(): + button.when_pressed = sound.play + +pause() diff --git a/docs/examples/negated.py b/docs/examples/negated.py new file mode 100644 index 000000000..91ae16d97 --- /dev/null +++ b/docs/examples/negated.py @@ -0,0 +1,10 @@ +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() diff --git a/docs/examples/pot_1.py b/docs/examples/pot_1.py new file mode 100644 index 000000000..1a8e7036c --- /dev/null +++ b/docs/examples/pot_1.py @@ -0,0 +1,6 @@ +from gpiozero import MCP3008 + +pot = MCP3008(channel=0) + +while True: + print(pot.value) diff --git a/docs/examples/pot_2.py b/docs/examples/pot_2.py new file mode 100644 index 000000000..b51d243d0 --- /dev/null +++ b/docs/examples/pot_2.py @@ -0,0 +1,9 @@ +from gpiozero import LEDBarGraph, MCP3008 +from signal import pause + +graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) +pot = MCP3008(channel=0) + +graph.source = pot + +pause() diff --git a/docs/examples/pwmled_pot.py b/docs/examples/pwmled_pot.py new file mode 100644 index 000000000..98502440a --- /dev/null +++ b/docs/examples/pwmled_pot.py @@ -0,0 +1,9 @@ +from gpiozero import PWMLED, MCP3008 +from signal import pause + +led = PWMLED(17) +pot = MCP3008() + +led.source = pot + +pause() diff --git a/docs/examples/pwmled_pot_values.py b/docs/examples/pwmled_pot_values.py new file mode 100644 index 000000000..00e1e5ced --- /dev/null +++ b/docs/examples/pwmled_pot_values.py @@ -0,0 +1,9 @@ +from gpiozero import PWMLED, MCP3008 +from signal import pause + +led = PWMLED(17) +pot = MCP3008() + +led.source = pot.values + +pause() diff --git a/docs/examples/random_values.py b/docs/examples/random_values.py new file mode 100644 index 000000000..524e4562e --- /dev/null +++ b/docs/examples/random_values.py @@ -0,0 +1,9 @@ +from gpiozero import PWMLED +from gpiozero.tools import random_values +from signal import pause + +led = PWMLED(4) +led.source = random_values() +led.source_delay = 0.1 + +pause() diff --git a/docs/examples/reaction_game.py b/docs/examples/reaction_game.py new file mode 100644 index 000000000..f8e3a52cc --- /dev/null +++ b/docs/examples/reaction_game.py @@ -0,0 +1,22 @@ +from gpiozero import Button, LED +from time import sleep +import random + +led = LED(17) + +player_1 = Button(2) +player_2 = Button(3) + +time = random.uniform(5, 10) +sleep(time) +led.on() + +while True: + if player_1.is_pressed: + print("Player 1 wins!") + break + if player_2.is_pressed: + print("Player 2 wins!") + break + +led.off() diff --git a/docs/examples/remote_button_robot.py b/docs/examples/remote_button_robot.py new file mode 100644 index 000000000..00582a057 --- /dev/null +++ b/docs/examples/remote_button_robot.py @@ -0,0 +1,27 @@ +from gpiozero import Button, Robot, Motor +from gpiozero.pins.pigpio import PiGPIOFactory +from signal import pause + +factory = PiGPIOFactory(host='192.168.1.17') +robot = Robot(left=Motor(4, 14), right=Motor(17, 18), + pin_factory=factory) # remote pins + +# local buttons +left = Button(26) +right = Button(16) +fw = Button(21) +bw = Button(20) + +fw.when_pressed = robot.forward +fw.when_released = robot.stop + +left.when_pressed = robot.left +left.when_released = robot.stop + +right.when_pressed = robot.right +right.when_released = robot.stop + +bw.when_pressed = robot.backward +bw.when_released = robot.stop + +pause() diff --git a/docs/examples/rgbled.py b/docs/examples/rgbled.py new file mode 100644 index 000000000..b80c9dc9d --- /dev/null +++ b/docs/examples/rgbled.py @@ -0,0 +1,28 @@ +from gpiozero import RGBLED +from time import sleep + +led = RGBLED(red=9, green=10, blue=11) + +led.red = 1 # full red +sleep(1) +led.red = 0.5 # half red +sleep(1) + +led.color = (0, 1, 0) # full green +sleep(1) +led.color = (1, 0, 1) # magenta +sleep(1) +led.color = (1, 1, 0) # yellow +sleep(1) +led.color = (0, 1, 1) # cyan +sleep(1) +led.color = (1, 1, 1) # white +sleep(1) + +led.color = (0, 0, 0) # off +sleep(1) + +# slowly increase intensity of blue +for n in range(100): + led.blue = n/100 + sleep(0.1) diff --git a/docs/examples/rgbled_pot_1.py b/docs/examples/rgbled_pot_1.py new file mode 100644 index 000000000..1efa5d0ae --- /dev/null +++ b/docs/examples/rgbled_pot_1.py @@ -0,0 +1,11 @@ +from gpiozero import RGBLED, MCP3008 + +led = RGBLED(red=2, green=3, blue=4) +red_pot = MCP3008(channel=0) +green_pot = MCP3008(channel=1) +blue_pot = MCP3008(channel=2) + +while True: + led.red = red_pot.value + led.green = green_pot.value + led.blue = blue_pot.value diff --git a/docs/examples/rgbled_pot_2.py b/docs/examples/rgbled_pot_2.py new file mode 100644 index 000000000..f3987ecd8 --- /dev/null +++ b/docs/examples/rgbled_pot_2.py @@ -0,0 +1,12 @@ +from gpiozero import RGBLED, MCP3008 +from gpiozero.tools import zip_values +from signal import pause + +led = RGBLED(2, 3, 4) +red_pot = MCP3008(0) +green_pot = MCP3008(1) +blue_pot = MCP3008(2) + +led.source = zip_values(red_pot, green_pot, blue_pot) + +pause() diff --git a/docs/examples/robot_1.py b/docs/examples/robot_1.py new file mode 100644 index 000000000..c61db1492 --- /dev/null +++ b/docs/examples/robot_1.py @@ -0,0 +1,10 @@ +from gpiozero import Robot, Motor +from time import sleep + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +for i in range(4): + robot.forward() + sleep(10) + robot.right() + sleep(1) diff --git a/docs/examples/robot_2.py b/docs/examples/robot_2.py new file mode 100644 index 000000000..ce6fd647a --- /dev/null +++ b/docs/examples/robot_2.py @@ -0,0 +1,9 @@ +from gpiozero import Robot, Motor, DistanceSensor +from signal import pause + +sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2) +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +sensor.when_in_range = robot.backward +sensor.when_out_of_range = robot.stop +pause() diff --git a/docs/examples/robot_buttons_1.py b/docs/examples/robot_buttons_1.py new file mode 100644 index 000000000..bf86e42df --- /dev/null +++ b/docs/examples/robot_buttons_1.py @@ -0,0 +1,23 @@ +from gpiozero import Robot, Motor, Button +from signal import pause + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +left = Button(26) +right = Button(16) +fw = Button(21) +bw = Button(20) + +fw.when_pressed = robot.forward +fw.when_released = robot.stop + +left.when_pressed = robot.left +left.when_released = robot.stop + +right.when_pressed = robot.right +right.when_released = robot.stop + +bw.when_pressed = robot.backward +bw.when_released = robot.stop + +pause() diff --git a/docs/examples/robot_buttons_2.py b/docs/examples/robot_buttons_2.py new file mode 100644 index 000000000..c49ce911b --- /dev/null +++ b/docs/examples/robot_buttons_2.py @@ -0,0 +1,34 @@ +from gpiozero import Button, Robot, Motor +from time import sleep +from signal import pause + +robot = Robot(Motor(17, 18), Motor(22, 23)) + +left = Button(2) +right = Button(3) +forward = Button(4) +backward = Button(5) +go = Button(6) + +instructions = [] + +def add_instruction(btn): + instructions.append({ + left: (-1, 1), + right: (1, -1), + forward: (1, 1), + backward: (-1, -1), + }[btn]) + +def do_instructions(): + instructions.append((0, 0)) + robot.source_delay = 0.5 + robot.source = instructions + sleep(robot.source_delay * len(instructions)) + del instructions[:] + +go.when_pressed = do_instructions +for button in (left, right, forward, backward): + button.when_pressed = add_instruction + +pause() diff --git a/docs/examples/robot_keyboard_1.py b/docs/examples/robot_keyboard_1.py new file mode 100644 index 000000000..186d40570 --- /dev/null +++ b/docs/examples/robot_keyboard_1.py @@ -0,0 +1,34 @@ +import curses +from gpiozero import Robot, Motor + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +actions = { + curses.KEY_UP: robot.forward, + curses.KEY_DOWN: robot.backward, + curses.KEY_LEFT: robot.left, + curses.KEY_RIGHT: robot.right, +} + +def main(window): + next_key = None + while True: + curses.halfdelay(1) + if next_key is None: + key = window.getch() + else: + key = next_key + next_key = None + if key != -1: + # KEY PRESSED + curses.halfdelay(3) + action = actions.get(key) + if action is not None: + action() + next_key = key + while next_key == key: + next_key = window.getch() + # KEY RELEASED + robot.stop() + +curses.wrapper(main) diff --git a/docs/examples/robot_keyboard_2.py b/docs/examples/robot_keyboard_2.py new file mode 100644 index 000000000..cb40c50a4 --- /dev/null +++ b/docs/examples/robot_keyboard_2.py @@ -0,0 +1,36 @@ +from gpiozero import Robot, Motor +from evdev import InputDevice, list_devices, ecodes + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +# Get the list of available input devices +devices = [InputDevice(device) for device in list_devices()] +# Filter out everything that's not a keyboard. Keyboards are defined as any +# device which has keys, and which specifically has keys 1..31 (roughly Esc, +# the numeric keys, the first row of QWERTY plus a few more) and which does +# *not* have key 0 (reserved) +must_have = {i for i in range(1, 32)} +must_not_have = {0} +devices = [ + dev + for dev in devices + for keys in (set(dev.capabilities().get(ecodes.EV_KEY, [])),) + if must_have.issubset(keys) + and must_not_have.isdisjoint(keys) +] +# Pick the first keyboard +keyboard = devices[0] + +keypress_actions = { + ecodes.KEY_UP: robot.forward, + ecodes.KEY_DOWN: robot.backward, + ecodes.KEY_LEFT: robot.left, + ecodes.KEY_RIGHT: robot.right, +} + +for event in keyboard.read_loop(): + if event.type == ecodes.EV_KEY and event.code in keypress_actions: + if event.value == 1: # key pressed + keypress_actions[event.code]() + if event.value == 0: # key released + robot.stop() diff --git a/docs/examples/robot_motion_1.py b/docs/examples/robot_motion_1.py new file mode 100644 index 000000000..e63dc017a --- /dev/null +++ b/docs/examples/robot_motion_1.py @@ -0,0 +1,10 @@ +from gpiozero import Robot, Motor, MotionSensor +from signal import pause + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) +pir = MotionSensor(5) + +pir.when_motion = robot.forward +pir.when_no_motion = robot.stop + +pause() diff --git a/docs/examples/robot_motion_2.py b/docs/examples/robot_motion_2.py new file mode 100644 index 000000000..2021a9a36 --- /dev/null +++ b/docs/examples/robot_motion_2.py @@ -0,0 +1,10 @@ +from gpiozero import Robot, Motor, MotionSensor +from gpiozero.tools import zip_values +from signal import pause + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) +pir = MotionSensor(5) + +robot.source = zip_values(pir, pir) + +pause() diff --git a/docs/examples/robot_pots_1.py b/docs/examples/robot_pots_1.py new file mode 100644 index 000000000..7c5e0daa1 --- /dev/null +++ b/docs/examples/robot_pots_1.py @@ -0,0 +1,12 @@ +from gpiozero import Robot, Motor, MCP3008 +from gpiozero.tools import zip_values +from signal import pause + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +left_pot = MCP3008(0) +right_pot = MCP3008(1) + +robot.source = zip_values(left_pot, right_pot) + +pause() diff --git a/docs/examples/robot_pots_2.py b/docs/examples/robot_pots_2.py new file mode 100644 index 000000000..2118ec933 --- /dev/null +++ b/docs/examples/robot_pots_2.py @@ -0,0 +1,12 @@ +from gpiozero import Robot, Motor, MCP3008 +from gpiozero.tools import scaled +from signal import pause + +robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + +left_pot = MCP3008(0) +right_pot = MCP3008(1) + +robot.source = zip(scaled(left_pot, -1, 1), scaled(right_pot, -1, 1)) + +pause() diff --git a/docs/examples/sense_hat_remote.py b/docs/examples/sense_hat_remote.py new file mode 100644 index 000000000..93a18bc86 --- /dev/null +++ b/docs/examples/sense_hat_remote.py @@ -0,0 +1,11 @@ +from gpiozero import MotionSensor +from gpiozero.pins.pigpio import PiGPIOFactory +from sense_hat import SenseHat + +remote_factory = PiGPIOFactory(host='192.198.1.4') +pir = MotionSensor(4, pin_factory=remote_factory) # remote motion sensor +sense = SenseHat() # local sense hat + +while True: + pir.wait_for_motion() + sense.show_message(sense.temperature) diff --git a/docs/examples/sense_hat_remote_2.py b/docs/examples/sense_hat_remote_2.py new file mode 100644 index 000000000..9bf2527c5 --- /dev/null +++ b/docs/examples/sense_hat_remote_2.py @@ -0,0 +1,16 @@ +from gpiozero import LightSensor +from gpiozero.pins.pigpio import PiGPIOFactory +from sense_hat import SenseHat + +remote_factory = PiGPIOFactory(host='192.168.1.4') +light = LightSensor(4, pin_factory=remote_factory) # remote motion sensor +sense = SenseHat() # local sense hat + +blue = (0, 0, 255) +yellow = (255, 255, 0) + +while True: + if light.value > 0.5: + sense.clear(yellow) + else: + sense.clear(blue) diff --git a/docs/examples/servo_1.py b/docs/examples/servo_1.py new file mode 100644 index 000000000..a55bfa7c4 --- /dev/null +++ b/docs/examples/servo_1.py @@ -0,0 +1,12 @@ +from gpiozero import Servo +from time import sleep + +servo = Servo(17) + +while True: + servo.min() + sleep(2) + servo.mid() + sleep(2) + servo.max() + sleep(2) diff --git a/docs/examples/servo_2.py b/docs/examples/servo_2.py new file mode 100644 index 000000000..fb795a499 --- /dev/null +++ b/docs/examples/servo_2.py @@ -0,0 +1,10 @@ +from gpiozero import Servo, Button + +servo = Servo(17) +btn = Button(14) + +while True: + servo.min() + btn.wait_for_press() + servo.max() + btn.wait_for_press() diff --git a/docs/examples/servo_sweep.py b/docs/examples/servo_sweep.py new file mode 100644 index 000000000..a93875b56 --- /dev/null +++ b/docs/examples/servo_sweep.py @@ -0,0 +1,10 @@ +from gpiozero import Servo +from gpiozero.tools import sin_values +from signal import pause + +servo = Servo(17) + +servo.source = sin_values() +servo.source_delay = 0.1 + +pause() diff --git a/docs/examples/sin_values.py b/docs/examples/sin_values.py new file mode 100644 index 000000000..22d797d2f --- /dev/null +++ b/docs/examples/sin_values.py @@ -0,0 +1,13 @@ +from gpiozero import Motor, Servo, TonalBuzzer +from gpiozero.tools import sin_values +from signal import pause + +motor = Motor(2, 3) +servo = Servo(4) +buzzer = TonalBuzzer(5) + +motor.source = sin_values() +servo.source = motor +buzzer.source = motor + +pause() diff --git a/docs/examples/source_value_processing.py b/docs/examples/source_value_processing.py new file mode 100644 index 000000000..b21e8ec32 --- /dev/null +++ b/docs/examples/source_value_processing.py @@ -0,0 +1,13 @@ +from gpiozero import Button, LED +from signal import pause + +def opposite(device): + for value in device.values: + yield not value + +led = LED(4) +btn = Button(17) + +led.source = opposite(btn) + +pause() diff --git a/docs/examples/thermometer.py b/docs/examples/thermometer.py new file mode 100644 index 000000000..d18b6f76b --- /dev/null +++ b/docs/examples/thermometer.py @@ -0,0 +1,12 @@ +from gpiozero import MCP3008 +from time import sleep + +def convert_temp(gen): + for value in gen: + yield (value * 3.3 - 0.5) * 100 + +adc = MCP3008(channel=0) + +for temp in convert_temp(adc.values): + print('The temperature is', temp, 'C') + sleep(1) diff --git a/docs/examples/timed_heat_lamp.py b/docs/examples/timed_heat_lamp.py new file mode 100644 index 000000000..1591e671c --- /dev/null +++ b/docs/examples/timed_heat_lamp.py @@ -0,0 +1,11 @@ +from gpiozero import Energenie, TimeOfDay +from datetime import time +from signal import pause + +lamp = Energenie(1) +daytime = TimeOfDay(time(8), time(20)) + +daytime.when_activated = lamp.on +daytime.when_deactivated = lamp.off + +pause() diff --git a/docs/examples/traffic_lights_1.py b/docs/examples/traffic_lights_1.py new file mode 100644 index 000000000..d102b0b3d --- /dev/null +++ b/docs/examples/traffic_lights_1.py @@ -0,0 +1,20 @@ +from gpiozero import TrafficLights +from time import sleep + +lights = TrafficLights(2, 3, 4) + +lights.green.on() + +while True: + sleep(10) + lights.green.off() + lights.amber.on() + sleep(1) + lights.amber.off() + lights.red.on() + sleep(10) + lights.amber.on() + sleep(1) + lights.green.on() + lights.amber.off() + lights.red.off() diff --git a/docs/examples/traffic_lights_2.py b/docs/examples/traffic_lights_2.py new file mode 100644 index 000000000..a3f866c28 --- /dev/null +++ b/docs/examples/traffic_lights_2.py @@ -0,0 +1,20 @@ +from gpiozero import TrafficLights +from time import sleep +from signal import pause + +lights = TrafficLights(2, 3, 4) + +def traffic_light_sequence(): + while True: + yield (0, 0, 1) # green + sleep(10) + yield (0, 1, 0) # amber + sleep(1) + yield (1, 0, 0) # red + sleep(10) + yield (1, 1, 0) # red+amber + sleep(1) + +lights.source = traffic_light_sequence() + +pause() diff --git a/docs/examples/traffic_lights_3.py b/docs/examples/traffic_lights_3.py new file mode 100644 index 000000000..8bf0aa521 --- /dev/null +++ b/docs/examples/traffic_lights_3.py @@ -0,0 +1,24 @@ +from gpiozero import LED +from time import sleep + +red = LED(2) +amber = LED(3) +green = LED(4) + +green.on() +amber.off() +red.off() + +while True: + sleep(10) + green.off() + amber.on() + sleep(1) + amber.off() + red.on() + sleep(10) + amber.on() + sleep(1) + green.on() + amber.off() + red.off() diff --git a/docs/examples/traffichat_remote_1.py b/docs/examples/traffichat_remote_1.py new file mode 100644 index 000000000..0bc74d6d5 --- /dev/null +++ b/docs/examples/traffichat_remote_1.py @@ -0,0 +1,7 @@ +import gpiozero +from gpiozero import TrafficHat +from gpiozero.pins.pigpio import PiGPIOFactory +from time import sleep + +gpiozero.Device.pin_factory = PiGPIOFactory(host='192.168.1.3') +th = TrafficHat() # traffic hat on 192.168.1.3 using remote pins diff --git a/docs/examples/traffichat_remote_2.py b/docs/examples/traffichat_remote_2.py new file mode 100644 index 000000000..b46a147dc --- /dev/null +++ b/docs/examples/traffichat_remote_2.py @@ -0,0 +1,9 @@ +import gpiozero +from gpiozero import TrafficHat +from gpiozero.pins.pigpio import PiGPIOFactory +from time import sleep + +remote_factory = PiGPIOFactory(host='192.168.1.3') + +th_1 = TrafficHat() # traffic hat using local pins +th_2 = TrafficHat(pin_factory=remote_factory) # traffic hat on 192.168.1.3 using remote pins diff --git a/docs/examples/whos_home_leds.py b/docs/examples/whos_home_leds.py new file mode 100644 index 000000000..528144474 --- /dev/null +++ b/docs/examples/whos_home_leds.py @@ -0,0 +1,22 @@ +from gpiozero import PingServer, LEDBoard +from gpiozero.tools import negated +from signal import pause + +status = LEDBoard( + mum=LEDBoard(red=14, green=15), + dad=LEDBoard(red=17, green=18), + alice=LEDBoard(red=21, green=22) +) + +statuses = { + PingServer('192.168.1.5'): status.mum, + PingServer('192.168.1.6'): status.dad, + PingServer('192.168.1.7'): status.alice, +} + +for server, leds in statuses.items(): + leds.green.source = server + leds.green.source_delay = 60 + leds.red.source = negated(leds.green) + +pause() diff --git a/docs/examples/whos_home_status.py b/docs/examples/whos_home_status.py new file mode 100644 index 000000000..6922c568b --- /dev/null +++ b/docs/examples/whos_home_status.py @@ -0,0 +1,18 @@ +from gpiozero import PingServer, StatusZero +from gpiozero.tools import negated +from signal import pause + +status = StatusZero('mum', 'dad', 'alice') + +statuses = { + PingServer('192.168.1.5'): status.mum, + PingServer('192.168.1.6'): status.dad, + PingServer('192.168.1.7'): status.alice, +} + +for server, leds in statuses.items(): + leds.green.source = server + leds.green.source_delay = 60 + leds.red.source = negated(leds.green) + +pause() diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 000000000..d65bd8802 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,433 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2017-2022 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +.. _faq: + +========================== +Frequently Asked Questions +========================== + +.. currentmodule:: gpiozero + + +.. _keep-your-script-running: + +How do I keep my script running? +================================ + +The following script looks like it should turn an :class:`LED` on:: + + from gpiozero import LED + + led = LED(17) + led.on() + +And it does, if you're using the Python or IPython shell, or the IDLE, Thonny or +Mu editors. However, if you saved this script as a Python file and ran it, it +would flash on briefly, then the script would end and it would turn off. + +The following file includes an intentional :func:`~signal.pause` to keep the +script alive:: + + from gpiozero import LED + from signal import pause + + led = LED(17) + led.on() + + pause() + +Now the script will stay running, leaving the LED on, until it is terminated +manually (e.g. by pressing Ctrl+C). Similarly, when setting up callbacks on +button presses or other input devices, the script needs to be running for the +events to be detected:: + + from gpiozero import Button + from signal import pause + + def hello(): + print("Hello") + + button = Button(2) + button.when_pressed = hello + + pause() + + +What's the difference between when_pressed, is_pressed and wait_for_press? +========================================================================== + +gpiozero provides a range of different approaches to reading input devices. +Sometimes you want to ask if a button's pressed, sometimes you want to do +something until it's pressed, and sometimes you want something to happen *when* +it's been pressed, regardless of what else is going on. + +In a simple example where the button is the only device in play, all of the +options would be equally effective. But as soon as you introduce an extra +element, like another GPIO device, you might need to choose the right approach +depending on your use case. + +* :attr:`~gpiozero.Button.is_pressed` is an attribute which reveals whether the + button is currently pressed by returning ``True`` or ``False``:: + + while True: + if btn.is_pressed: + print("Pressed") + else: + print("Not pressed") + +* :meth:`~gpiozero.Button.wait_for_press()` is a method which blocks the code from + continuing until the button is pressed. Also see + :meth:`~gpiozero.Button.wait_for_release()`:: + + while True: + print("Released. Waiting for press..") + btn.wait_for_press() + print("Pressed. Waiting for release...") + btn.wait_for_release() + +* :attr:`~gpiozero.Button.when_pressed` is an attribute which assigns a callback + function to the event of the button being pressed. Every time the button is + pressed, the callback function is executed in a separate thread. Also see + :attr:`~gpiozero.Button.when_released`:: + + def pressed(): + print("Pressed") + + def released(): + print("Released") + + btn.when_pressed = pressed + btn.when_released = released + +This pattern of options is common among many devices. All :doc:`input devices +` and :doc:`internal devices ` have ``is_active``, +``when_activated``, ``when_deactivated``, ``wait_for_active`` and +``wait_for_inactive``, and many provide aliases (such as "pressed" for +"activated"). + +Also see a more advanced approach in the :doc:`source_values` page. + + +My event handler isn't being called +=================================== + +When assigning event handlers, don't call the function you're assigning. For +example:: + + from gpiozero import Button + + def pushed(): + print("Don't push the button!") + + b = Button(17) + b.when_pressed = pushed() + +In the case above, when assigning to :attr:`~Button.when_pressed`, the thing +that is assigned is the *result of calling* the ``pushed`` function. Because +``pushed`` doesn't explicitly return anything, the result is :data:`None`. +Hence this is equivalent to doing:: + + b.when_pressed = None + +This doesn't raise an error because it's perfectly valid: it's what you assign +when you don't want the event handler to do anything. Instead, you want to +do the following:: + + b.when_pressed = pushed + +This will assign the function to the event handler *without calling it*. This +is the crucial difference between ``my_function`` (a reference to a function) +and ``my_function()`` (the result of calling a function). + +.. note:: + + Note that as of v1.5, setting a callback to :data:`None` when it was + previously :data:`None` will raise a :class:`CallbackSetToNone` warning, + with the intention of alerting users when callbacks are set to :data:`None` + accidentally. However, if this is intentional, the warning can be + suppressed. See the :mod:`warnings` module for reference. + +.. _pinfactoryfallback-warnings: + +Why do I get PinFactoryFallback warnings when I import gpiozero? +================================================================ + +You are most likely working in a virtual Python environment and have forgotten +to install a pin driver library like ``RPi.GPIO``. GPIO Zero relies upon lower +level pin drivers to handle interfacing to the GPIO pins on the Raspberry Pi, +so you can eliminate the warning simply by installing GPIO Zero's first +preference: + +.. code-block:: console + + $ pip install rpi.gpio + +When GPIO Zero is imported it attempts to find a pin driver by importing them +in a preferred order (detailed in :doc:`api_pins`). If it fails to load its +first preference (``RPi.GPIO``) it notifies you with a warning, then falls back +to trying its second preference and so on. Eventually it will fall back all the +way to the ``native`` implementation. This is a pure Python implementation +built into GPIO Zero itself. While this will work for most things it's almost +certainly not what you want (it doesn't support PWM, and it's quite slow at +certain things). + +If you want to use a pin driver other than the default, and you want to +suppress the warnings you've got a couple of options: + +1. Explicitly specify what pin driver you want via the + :envvar:`GPIOZERO_PIN_FACTORY` environment variable. For example: + + .. code-block:: console + + $ GPIOZERO_PIN_FACTORY=pigpio python3 + + In this case no warning is issued because there's no fallback; either the + specified factory loads or it fails in which case an :exc:`ImportError` will + be raised. + +2. Suppress the warnings and let the fallback mechanism work: + + .. code-block:: pycon + + >>> import warnings + >>> warnings.simplefilter('ignore') + >>> import gpiozero + + Refer to the :mod:`warnings` module documentation for more refined ways to + filter out specific warning classes. + + +How can I tell what version of gpiozero I have installed? +========================================================= + +The gpiozero library relies on the setuptools package for installation +services. You can use the setuptools :mod:`pkg_resources` API to query which +version of gpiozero is available in your Python environment like so: + +.. code-block:: pycon + + >>> from pkg_resources import require + >>> require('gpiozero') + [gpiozero 1.6.2 (/usr/lib/python3/dist-packages)] + >>> require('gpiozero')[0].version + '1.6.2' + +If you have multiple versions installed (e.g. from :command:`pip` and +:command:`apt`) they will not show up in the list returned by the +:meth:`pkg_resources.require` method. However, the first entry in the list will +be the version that ``import gpiozero`` will import. + +If you receive the error "No module named pkg_resources", you need to install +:command:`pip`. This can be done with the following command in Raspberry Pi OS: + +.. code-block:: console + + $ sudo apt install python3-pip + +Alternatively, install pip with `get-pip`_. + + +Why do I get "command not found" when running pinout? +===================================================== + +The gpiozero library is available as a Debian package for Python 2 and Python +3, but the :doc:`cli_pinout` tool cannot be made available by both packages, so +it's only included with the Python 3 version of the package. To make sure the +:doc:`cli_pinout` tool is available, the "python3-gpiozero" package must be +installed: + +.. code-block:: console + + $ sudo apt install python3-gpiozero + +Alternatively, installing gpiozero using :command:`pip` will install the +command line tool, regardless of Python version: + +.. code-block:: console + + $ sudo pip3 install gpiozero + +or: + +.. code-block:: console + + $ sudo pip install gpiozero + + +The pinout command line tool incorrectly identifies my Raspberry Pi model +========================================================================= + +If your Raspberry Pi model is new, it's possible it wasn't known about at the +time of the gpiozero release you are using. Ensure you have the latest version +installed (remember, the :doc:`cli_pinout` tool usually comes from the Python 3 +version of the package as noted in the previous FAQ). + +If the Pi model you are using isn't known to gpiozero, it may have been added +since the last release. You can check the `GitHub issues`_ to see if it's been +reported before, or check the `commits`_ on GitHub since the last release to +see if it's been added. The model determination can be found in +:file:`gpiozero/pins/data.py`. + + +.. _gpio-cleanup: + +What's the gpiozero equivalent of GPIO.cleanup()? +================================================= + +Many people ask how to do the equivalent of the ``cleanup`` function from +``RPi.GPIO``. In gpiozero, at the end of your script, cleanup is run +automatically, restoring your GPIO pins to the state they were found. + +To explicitly close a connection to a pin, you can manually call the +:meth:`~Device.close` method on a device object: + +.. code-block:: pycon + + >>> led = LED(2) + >>> led.on() + >>> led + + >>> led.close() + >>> led + + +This means that you can reuse the pin for another device, and that despite +turning the LED on (and hence, the pin high), after calling +:meth:`~Device.close` it is restored to its previous state (LED off, pin low). + +Read more about :ref:`migrating_from_rpigpio`. + + +How do I use button.when_pressed and button.when_held together? +=============================================================== + +The :class:`Button` class provides a :attr:`~Button.when_held` property which +is used to set a callback for when the button is held down for a set amount of +time (as determined by the :attr:`~Button.hold_time` property). If you want to +set :attr:`~Button.when_held` as well as :attr:`~Button.when_pressed`, you'll +notice that both callbacks will fire. Sometimes, this is acceptable, but often +you'll want to only fire the :attr:`~Button.when_pressed` callback when the +button has not been held, only pressed. + +The way to achieve this is to *not* set a callback on +:attr:`~Button.when_pressed`, and instead use :attr:`~Button.when_released` to +work out whether it had been held or just pressed:: + + from gpiozero import Button + + Button.was_held = False + + def held(btn): + btn.was_held = True + print("button was held not just pressed") + + def released(btn): + if not btn.was_held: + pressed() + btn.was_held = False + + def pressed(): + print("button was pressed not held") + + btn = Button(2) + + btn.when_held = held + btn.when_released = released + + +Why do I get "ImportError: cannot import name" when trying to import from gpiozero? +=================================================================================== + +It's common to see people name their first gpiozero script ``gpiozero.py``. +Unfortunately, this will cause your script to try to import itself, rather than +the gpiozero library from the libraries path. You'll see an error like this:: + + Traceback (most recent call last): + File "gpiozero.py", line 1, in + from gpiozero import LED + File "/home/pi/gpiozero.py", line 1, in + from gpiozero import LED + ImportError: cannot import name 'LED' + +Simply rename your script to something else, and run it again. Be sure not to +name any of your scripts the same name as a Python module you may be importing, +such as :file:`picamera.py`. + + +Why do I get an AttributeError trying to set attributes on a device object? +=========================================================================== + +If you try to add an attribute to a gpiozero device object after its +initialization, you'll find you can't: + +.. code-block:: pycon + + >>> from gpiozero import Button + >>> btn = Button(2) + >>> btn.label = 'alarm' + Traceback (most recent call last): + File "", line 1, in + File "/usr/lib/python3/dist-packages/gpiozero/devices.py", line 118, in __setattr__ + self.__class__.__name__, name)) + AttributeError: 'Button' object has no attribute 'label' + +This is in order to prevent users accidentally setting new attributes by +mistake. Because gpiozero provides functionality through setting attributes via +properties, such as callbacks on buttons (and often there is no immediate +feedback when setting a property), this could lead to bugs very difficult to +find. Consider the following example:: + + from gpiozero import Button + + def hello(): + print("hello") + + btn = Button(2) + + btn.pressed = hello + +This is perfectly valid Python code, and no errors would occur, but the program +would not behave as expected: pressing the button would do nothing, because the +property for setting a callback is ``when_pressed`` not ``pressed``. But +without gpiozero preventing this non-existent attribute from being set, the +user would likely struggle to see the mistake. + +If you really want to set a new attribute on a device object, you need to +create it in the class before initializing your object: + +.. code-block:: pycon + + >>> from gpiozero import Button + >>> Button.label = '' + >>> btn = Button(2) + >>> btn.label = 'alarm' + >>> def press(btn): + ...: print(btn.label, "was pressed") + >>> btn.when_pressed = press + + +Why is it called GPIO Zero? Does it only work on Pi Zero? +========================================================= + +gpiozero works on all Raspberry Pi models, not just the Pi Zero. + +The "zero" is part of a naming convention for "zero-boilerplate" education +friendly libraries, which started with `Pygame Zero`_, and has been followed by +`NetworkZero`_, `guizero`_ and more. + +These libraries aim to remove barrier to entry and provide a smooth learning +curve for beginners by making it easy to get started and easy to build up to +more advanced projects. + + +.. _get-pip: https://pip.pypa.io/en/stable/installing/ +.. _GitHub issues: https://github.com/gpiozero/gpiozero/issues +.. _commits: https://github.com/gpiozero/gpiozero/commits/master +.. _Pygame Zero: https://pygame-zero.readthedocs.io/en/stable/ +.. _NetworkZero: https://networkzero.readthedocs.io/en/latest/ +.. _guizero: https://lawsie.github.io/guizero/ diff --git a/docs/images/7seg_multi.fzz b/docs/images/7seg_multi.fzz new file mode 100644 index 000000000..1de6500a4 Binary files /dev/null and b/docs/images/7seg_multi.fzz differ diff --git a/docs/images/7seg_multi_bb.pdf b/docs/images/7seg_multi_bb.pdf new file mode 100644 index 000000000..da52165a6 Binary files /dev/null and b/docs/images/7seg_multi_bb.pdf differ diff --git a/docs/images/7seg_multi_bb.png b/docs/images/7seg_multi_bb.png new file mode 100644 index 000000000..97b6f56a4 Binary files /dev/null and b/docs/images/7seg_multi_bb.png differ diff --git a/docs/images/7seg_multi_bb.svg b/docs/images/7seg_multi_bb.svg new file mode 100644 index 000000000..a4ddc35bd --- /dev/null +++ b/docs/images/7seg_multi_bb.svg @@ -0,0 +1,6283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + 35 + + + 35 + + + 40 + + + 40 + + + 45 + + + 45 + + + 50 + + + 50 + + + 55 + + + 55 + + + 60 + + + 60 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/7seg_multi_schem.pdf b/docs/images/7seg_multi_schem.pdf new file mode 100644 index 000000000..e8aaad0ee Binary files /dev/null and b/docs/images/7seg_multi_schem.pdf differ diff --git a/docs/images/7seg_multi_schem.png b/docs/images/7seg_multi_schem.png new file mode 100644 index 000000000..87552038d Binary files /dev/null and b/docs/images/7seg_multi_schem.png differ diff --git a/docs/images/7seg_multi_schem.svg b/docs/images/7seg_multi_schem.svg new file mode 100644 index 000000000..d2d7c9fc7 --- /dev/null +++ b/docs/images/7seg_multi_schem.svg @@ -0,0 +1,1951 @@ + + + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + +7 SEGMENT + + +4DIGIT + + +YOUNGSUN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A + + +B + + +C + + +D + + +E + + +F + + +G + + +DP + + +A + + +B + + +C + + +D + + +E + + +F + + +G + + +DP + + +A + + +B + + +C + + +D + + +E + + +F + + +G + + +DP + + +A + + +B + + +C + + +D + + +E + + +F + + +G + + +DP + + +COL + + +APOS + + + + + + + + +2 + + + + + + + + + + +DIG2 + + + + + + + + + + + + +6 + + + + + + + + + + +DIG3 + + + + + + + + + + + + +8 + + + + + + + + + + +DIG4 + + + + + + + + + + + + +4 + + + + + + + + + + +COL-A + + + + + + + + + + + + +10 + + + + + + + + + + +APOS-A + + + + + + + + + + + + +14 + + + + + + + + + + +A + + + + + + + + + + + + +16 + + + + + + + + + + +B + + + + + + + + + + + + +13 + + + + + + + + + + +C + + + + + + + + + + + + +3 + + + + + + + + + + +D + + + + + + + + + + + + +5 + + + + + + + + + + +E + + + + + + + + + + + + +11 + + + + + + + + + + +F + + + + + + + + + + + + +15 + + + + + + + + + + +G + + + + + + + + + + + + +7 + + + + + + + + + + +DP + + + + + + + + + + + + +12 + + + + + + + + + + +COL-C + + + + + + + + + + + + +9 + + + + + + + + + + +APOS-C + + + + + + + + + + + + +1 + + + + + + + + + + +DIG1 + + + + + + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + + + +1 + + + + + + +2 + + + + + + + + + + +3 + + + + + + + + + + + + + + + + + + + +1 + + + + + + +2 + + + + + + + + + + +3 + + + + + + + + + + + + + + + + + + + +1 + + + + + + +2 + + + + + + + + + + +3 + + + + + + + + + + + + + + + + + + + +1 + + + + + + +2 + + + + + + + + + + +3 + + + + + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + +2 + + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +G + + +P + + +I + + +O + + +1 + + +4 + + + + + +U + + +A + + +R + + +T + + +0 + + +_ + + +T + + +X + + +D + + + + + + + +G + + +P + + +I + + +O + + +1 + + +5 + + + + + +U + + +A + + +R + + +T + + +0 + + +_ + + +R + + +X + + +D + + + + +G + + +P + + +I + + +O + + +1 + + +8 + + + + + +P + + +C + + +M + + +_ + + +C + + +L + + +K + + + + +G + + +P + + +I + + +O + + +2 + + +3 + + + + +G + + +P + + +I + + +O + + +2 + + +4 + + + + +G + + +P + + +I + + +O + + +2 + + +5 + + + + +G + + +P + + +I + + +O + + +8 + + + + + +S + + +P + + +I + + +0 + + +_ + + +C + + +E + + +0 + + +_ + + +N + + + + +G + + +P + + +I + + +O + + +7 + + + + + +S + + +P + + +I + + +0 + + +_ + + +C + + +E + + +1 + + +_ + + +N + + + + +ID_SC I2C ID EEPROM + + + + +G + + +P + + +I + + +O + + +1 + + +2 + + + + +G + + +P + + +I + + +O + + +1 + + +6 + + + + +G + + +P + + +I + + +O + + +2 + + +0 + + + + +G + + +P + + +I + + +O + + +2 + + +1 + + + + +G + + +P + + +I + + +O + + +2 + + + + + +S + + +D + + +A + + +1 + + + + + +I + + +2 + + +C + + +G + + +P + + +I + + +O + + +3 + + + + + +S + + +C + + +L + + +1 + + + + + +I + + +2 + + +C + + +G + + +P + + +I + + +O + + +4 + + +G + + +P + + +I + + +O + + +1 + + +7 + + +G + + +P + + +I + + +O + + +2 + + +7 + + +G + + +P + + +I + + +O + + +2 + + +2 + + +GIPO10 SPI0_MOSI + + +G + + +P + + +I + + +O + + +9 + + + + + +S + + +P + + +I + + +0 + + +_ + + +M + + +I + + +S + + +O + + +G + + +P + + +I + + +O + + +1 + + +1 + + + + + +S + + +P + + +I + + +0 + + +_ + + +S + + +C + + +L + + +K + + +ID_SD I2C ID EEPROM + + +G + + +P + + +I + + +O + + +5 + + +G + + +P + + +I + + +O + + +5 + + +G + + +P + + +I + + +O + + +6 + + +G + + +P + + +I + + +O + + +1 + + +3 + + +G + + +P + + +I + + +O + + +1 + + +9 + + +G + + +P + + +I + + +O + + +2 + + +6 + + +GND + + +3V3 + + +5V + + + +RaspberryPi + + +Model 2 v1.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +R1-R8330ΩQ1-Q42N 3904R9-R1233kΩ \ No newline at end of file diff --git a/docs/images/button.fzz b/docs/images/button.fzz new file mode 100644 index 000000000..abddc8506 Binary files /dev/null and b/docs/images/button.fzz differ diff --git a/docs/images/button_bb.pdf b/docs/images/button_bb.pdf new file mode 100644 index 000000000..ef43a6a86 Binary files /dev/null and b/docs/images/button_bb.pdf differ diff --git a/docs/images/button_bb.png b/docs/images/button_bb.png new file mode 100644 index 000000000..0cdb58a06 Binary files /dev/null and b/docs/images/button_bb.png differ diff --git a/docs/images/button_bb.svg b/docs/images/button_bb.svg new file mode 100644 index 000000000..b0940ab8f --- /dev/null +++ b/docs/images/button_bb.svg @@ -0,0 +1,4517 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/button_robot.fzz b/docs/images/button_robot.fzz new file mode 100644 index 000000000..63e3ca424 Binary files /dev/null and b/docs/images/button_robot.fzz differ diff --git a/docs/images/button_robot_bb.pdf b/docs/images/button_robot_bb.pdf new file mode 100644 index 000000000..45c0e3eb9 Binary files /dev/null and b/docs/images/button_robot_bb.pdf differ diff --git a/docs/images/button_robot_bb.png b/docs/images/button_robot_bb.png new file mode 100644 index 000000000..72e486b3a Binary files /dev/null and b/docs/images/button_robot_bb.png differ diff --git a/docs/images/button_robot_bb.svg b/docs/images/button_robot_bb.svg new file mode 100644 index 000000000..b889bfa63 --- /dev/null +++ b/docs/images/button_robot_bb.svg @@ -0,0 +1,4755 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/color_picker.fzz b/docs/images/color_picker.fzz new file mode 100644 index 000000000..66438efd4 Binary files /dev/null and b/docs/images/color_picker.fzz differ diff --git a/docs/images/color_picker_bb.pdf b/docs/images/color_picker_bb.pdf new file mode 100644 index 000000000..f7a85dd31 Binary files /dev/null and b/docs/images/color_picker_bb.pdf differ diff --git a/docs/images/color_picker_bb.png b/docs/images/color_picker_bb.png new file mode 100644 index 000000000..bca4e1419 Binary files /dev/null and b/docs/images/color_picker_bb.png differ diff --git a/docs/images/color_picker_bb.svg b/docs/images/color_picker_bb.svg new file mode 100644 index 000000000..77f669965 --- /dev/null +++ b/docs/images/color_picker_bb.svg @@ -0,0 +1,4632 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/composed_devices.dot b/docs/images/composed_devices.dot new file mode 100644 index 000000000..1e41552ad --- /dev/null +++ b/docs/images/composed_devices.dot @@ -0,0 +1,44 @@ +/* vim: set et sw=4 sts=4: */ + +digraph classes { + graph [rankdir=TB]; + node [shape=rect, style=filled, color="#298029", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + edge [arrowhead=onormal, style=dashed]; + + RGBLED->LED; + RGBLED->PWMLED; + LEDBoard->LED; + LEDBoard->PWMLED; + LEDBarGraph->LED; + LEDBarGraph->PWMLED; + LEDCharDisplay->LED; + LEDCharDisplay->PWMLED; + + LEDMultiCharDisplay->LEDCharDisplay; + LEDMultiCharDisplay->DigitalOutputDevice; + + ButtonBoard->Button; + + TrafficLightsBuzzer->TrafficLights; + TrafficLightsBuzzer->Buzzer; + TrafficLightsBuzzer->Button; + + StatusBoard->LEDBoard; + StatusBoard->Button; + + JamHat->LEDBoard; + JamHat->Button; + JamHat->TonalBuzzer; + + Robot->Motor; + Motor->DigitalOutputDevice; + Motor->PWMOutputDevice; + + PhaseEnableRobot->PhaseEnableMotor; + PhaseEnableMotor->DigitalOutputDevice; + PhaseEnableMotor->PWMOutputDevice; + + Servo->PWMOutputDevice; + + RotaryEncoder->InputDevice; +} diff --git a/docs/images/composed_devices.pdf b/docs/images/composed_devices.pdf new file mode 100644 index 000000000..016de0211 Binary files /dev/null and b/docs/images/composed_devices.pdf differ diff --git a/docs/images/composed_devices.png b/docs/images/composed_devices.png new file mode 100644 index 000000000..3cd11fa7a Binary files /dev/null and b/docs/images/composed_devices.png differ diff --git a/docs/images/composed_devices.svg b/docs/images/composed_devices.svg new file mode 100644 index 000000000..56a7d3468 --- /dev/null +++ b/docs/images/composed_devices.svg @@ -0,0 +1,319 @@ + + + + + + +classes + + + +RGBLED + +RGBLED + + + +LED + +LED + + + +RGBLED->LED + + + + + +PWMLED + +PWMLED + + + +RGBLED->PWMLED + + + + + +LEDBoard + +LEDBoard + + + +LEDBoard->LED + + + + + +LEDBoard->PWMLED + + + + + +LEDBarGraph + +LEDBarGraph + + + +LEDBarGraph->LED + + + + + +LEDBarGraph->PWMLED + + + + + +LEDCharDisplay + +LEDCharDisplay + + + +LEDCharDisplay->LED + + + + + +LEDCharDisplay->PWMLED + + + + + +LEDMultiCharDisplay + +LEDMultiCharDisplay + + + +LEDMultiCharDisplay->LEDCharDisplay + + + + + +DigitalOutputDevice + +DigitalOutputDevice + + + +LEDMultiCharDisplay->DigitalOutputDevice + + + + + +ButtonBoard + +ButtonBoard + + + +Button + +Button + + + +ButtonBoard->Button + + + + + +TrafficLightsBuzzer + +TrafficLightsBuzzer + + + +TrafficLightsBuzzer->Button + + + + + +TrafficLights + +TrafficLights + + + +TrafficLightsBuzzer->TrafficLights + + + + + +Buzzer + +Buzzer + + + +TrafficLightsBuzzer->Buzzer + + + + + +StatusBoard + +StatusBoard + + + +StatusBoard->LEDBoard + + + + + +StatusBoard->Button + + + + + +JamHat + +JamHat + + + +JamHat->LEDBoard + + + + + +JamHat->Button + + + + + +TonalBuzzer + +TonalBuzzer + + + +JamHat->TonalBuzzer + + + + + +Robot + +Robot + + + +Motor + +Motor + + + +Robot->Motor + + + + + +Motor->DigitalOutputDevice + + + + + +PWMOutputDevice + +PWMOutputDevice + + + +Motor->PWMOutputDevice + + + + + +PhaseEnableRobot + +PhaseEnableRobot + + + +PhaseEnableMotor + +PhaseEnableMotor + + + +PhaseEnableRobot->PhaseEnableMotor + + + + + +PhaseEnableMotor->DigitalOutputDevice + + + + + +PhaseEnableMotor->PWMOutputDevice + + + + + +Servo + +Servo + + + +Servo->PWMOutputDevice + + + + + +RotaryEncoder + +RotaryEncoder + + + +InputDevice + +InputDevice + + + +RotaryEncoder->InputDevice + + + + + diff --git a/docs/images/composite_device_hierarchy.dot b/docs/images/composite_device_hierarchy.dot new file mode 100644 index 000000000..5b8e3fdb6 --- /dev/null +++ b/docs/images/composite_device_hierarchy.dot @@ -0,0 +1,58 @@ +digraph classes { + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + + EventsMixin; + HoldMixin; + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + + CompositeDevice; + CompositeOutputDevice; + Device; + LEDCollection; + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + + ButtonBoard->CompositeDevice; + ButtonBoard->HoldMixin; + CamJamKitRobot->Robot; + CompositeDevice->Device; + CompositeOutputDevice->CompositeDevice; + Energenie->Device; + FishDish->CompositeOutputDevice; + HoldMixin->EventsMixin; + JamHat->CompositeOutputDevice; + LEDBarGraph->LEDCollection; + LEDBoard->LEDCollection; + LEDCharDisplay->LEDCollection; + LEDCollection->CompositeOutputDevice; + LEDMultiCharDisplay->CompositeOutputDevice; + PhaseEnableRobot->CompositeDevice; + PiHutXmasTree->LEDBoard; + PiLiter->LEDBoard; + PiLiterBarGraph->LEDBarGraph; + PiStop->TrafficLights; + PiTraffic->TrafficLights; + Pibrella->CompositeOutputDevice; + PololuDRV8835Robot->PhaseEnableRobot; + PumpkinPi->LEDBoard; + Robot->CompositeDevice; + RotaryEncoder->CompositeDevice; + RotaryEncoder->EventsMixin; + RyanteckRobot->Robot; + SnowPi->LEDBoard; + StatusBoard->CompositeOutputDevice; + StatusZero->LEDBoard; + TonalBuzzer->CompositeDevice; + TrafficHat->CompositeOutputDevice; + TrafficLights->LEDBoard; + TrafficLightsBuzzer->CompositeOutputDevice; + TrafficpHat->TrafficLights; +} diff --git a/docs/images/composite_device_hierarchy.pdf b/docs/images/composite_device_hierarchy.pdf new file mode 100644 index 000000000..7c09537eb Binary files /dev/null and b/docs/images/composite_device_hierarchy.pdf differ diff --git a/docs/images/composite_device_hierarchy.png b/docs/images/composite_device_hierarchy.png new file mode 100644 index 000000000..0f06f59b1 Binary files /dev/null and b/docs/images/composite_device_hierarchy.png differ diff --git a/docs/images/composite_device_hierarchy.svg b/docs/images/composite_device_hierarchy.svg new file mode 100644 index 000000000..8f31d6721 --- /dev/null +++ b/docs/images/composite_device_hierarchy.svg @@ -0,0 +1,433 @@ + + + + + + +classes + + + +EventsMixin + +EventsMixin + + + +HoldMixin + +HoldMixin + + + +HoldMixin->EventsMixin + + + + + +CompositeDevice + +CompositeDevice + + + +Device + +Device + + + +CompositeDevice->Device + + + + + +CompositeOutputDevice + +CompositeOutputDevice + + + +CompositeOutputDevice->CompositeDevice + + + + + +LEDCollection + +LEDCollection + + + +LEDCollection->CompositeOutputDevice + + + + + +ButtonBoard + +ButtonBoard + + + +ButtonBoard->HoldMixin + + + + + +ButtonBoard->CompositeDevice + + + + + +CamJamKitRobot + +CamJamKitRobot + + + +Robot + +Robot + + + +CamJamKitRobot->Robot + + + + + +Robot->CompositeDevice + + + + + +Energenie + +Energenie + + + +Energenie->Device + + + + + +FishDish + +FishDish + + + +FishDish->CompositeOutputDevice + + + + + +JamHat + +JamHat + + + +JamHat->CompositeOutputDevice + + + + + +LEDBarGraph + +LEDBarGraph + + + +LEDBarGraph->LEDCollection + + + + + +LEDBoard + +LEDBoard + + + +LEDBoard->LEDCollection + + + + + +LEDCharDisplay + +LEDCharDisplay + + + +LEDCharDisplay->LEDCollection + + + + + +LEDMultiCharDisplay + +LEDMultiCharDisplay + + + +LEDMultiCharDisplay->CompositeOutputDevice + + + + + +PhaseEnableRobot + +PhaseEnableRobot + + + +PhaseEnableRobot->CompositeDevice + + + + + +PiHutXmasTree + +PiHutXmasTree + + + +PiHutXmasTree->LEDBoard + + + + + +PiLiter + +PiLiter + + + +PiLiter->LEDBoard + + + + + +PiLiterBarGraph + +PiLiterBarGraph + + + +PiLiterBarGraph->LEDBarGraph + + + + + +PiStop + +PiStop + + + +TrafficLights + +TrafficLights + + + +PiStop->TrafficLights + + + + + +TrafficLights->LEDBoard + + + + + +PiTraffic + +PiTraffic + + + +PiTraffic->TrafficLights + + + + + +Pibrella + +Pibrella + + + +Pibrella->CompositeOutputDevice + + + + + +PololuDRV8835Robot + +PololuDRV8835Robot + + + +PololuDRV8835Robot->PhaseEnableRobot + + + + + +PumpkinPi + +PumpkinPi + + + +PumpkinPi->LEDBoard + + + + + +RotaryEncoder + +RotaryEncoder + + + +RotaryEncoder->EventsMixin + + + + + +RotaryEncoder->CompositeDevice + + + + + +RyanteckRobot + +RyanteckRobot + + + +RyanteckRobot->Robot + + + + + +SnowPi + +SnowPi + + + +SnowPi->LEDBoard + + + + + +StatusBoard + +StatusBoard + + + +StatusBoard->CompositeOutputDevice + + + + + +StatusZero + +StatusZero + + + +StatusZero->LEDBoard + + + + + +TonalBuzzer + +TonalBuzzer + + + +TonalBuzzer->CompositeDevice + + + + + +TrafficHat + +TrafficHat + + + +TrafficHat->CompositeOutputDevice + + + + + +TrafficLightsBuzzer + +TrafficLightsBuzzer + + + +TrafficLightsBuzzer->CompositeOutputDevice + + + + + +TrafficpHat + +TrafficpHat + + + +TrafficpHat->TrafficLights + + + + + diff --git a/docs/images/device_hierarchy.dot b/docs/images/device_hierarchy.dot new file mode 100644 index 000000000..82d80c47c --- /dev/null +++ b/docs/images/device_hierarchy.dot @@ -0,0 +1,134 @@ +digraph classes { + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + + EventsMixin; + HoldMixin; + SharedMixin; + SourceMixin; + ValuesMixin; + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + + AnalogInputDevice; + CompositeDevice; + CompositeOutputDevice; + Device; + GPIODevice; + InternalDevice; + LEDCollection; + MCP30xx; + MCP32xx; + MCP33xx; + MCP3xx2; + MCP3xxx; + SPIDevice; + SmoothedInputDevice; + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + + AnalogInputDevice->SPIDevice; + AngularServo->Servo; + Button->DigitalInputDevice; + Button->HoldMixin; + ButtonBoard->CompositeDevice; + ButtonBoard->HoldMixin; + Buzzer->DigitalOutputDevice; + CPUTemperature->PolledInternalDevice; + CamJamKitRobot->Robot; + CompositeDevice->Device; + CompositeOutputDevice->CompositeDevice; + CompositeOutputDevice->SourceMixin; + Device->ValuesMixin; + DigitalInputDevice->EventsMixin; + DigitalInputDevice->InputDevice; + DigitalOutputDevice->OutputDevice; + DiskUsage->PolledInternalDevice; + DistanceSensor->SmoothedInputDevice; + Energenie->Device; + Energenie->SourceMixin; + FishDish->CompositeOutputDevice; + GPIODevice->Device; + HoldMixin->EventsMixin; + InputDevice->GPIODevice; + InternalDevice->Device; + InternalDevice->EventsMixin; + JamHat->CompositeOutputDevice; + LED->DigitalOutputDevice; + LEDBarGraph->LEDCollection; + LEDBoard->LEDCollection; + LEDCharDisplay->LEDCollection; + LEDCollection->CompositeOutputDevice; + LEDMultiCharDisplay->CompositeOutputDevice; + LedBorg->RGBLED; + LightSensor->SmoothedInputDevice; + LineSensor->SmoothedInputDevice; + LoadAverage->PolledInternalDevice; + MCP3001->MCP30xx; + MCP3002->MCP30xx; + MCP3002->MCP3xx2; + MCP3004->MCP30xx; + MCP3008->MCP30xx; + MCP30xx->MCP3xxx; + MCP3201->MCP32xx; + MCP3202->MCP32xx; + MCP3202->MCP3xx2; + MCP3204->MCP32xx; + MCP3208->MCP32xx; + MCP32xx->MCP3xxx; + MCP3301->MCP33xx; + MCP3302->MCP33xx; + MCP3304->MCP33xx; + MCP33xx->MCP3xxx; + MCP3xx2->MCP3xxx; + MCP3xxx->AnalogInputDevice; + MotionSensor->SmoothedInputDevice; + Motor->CompositeDevice; + Motor->SourceMixin; + OutputDevice->GPIODevice; + OutputDevice->SourceMixin; + PWMLED->PWMOutputDevice; + PWMOutputDevice->OutputDevice; + PhaseEnableMotor->CompositeDevice; + PhaseEnableMotor->SourceMixin; + PhaseEnableRobot->CompositeDevice; + PhaseEnableRobot->SourceMixin; + PiHutXmasTree->LEDBoard; + PiLiter->LEDBoard; + PiLiterBarGraph->LEDBarGraph; + PiStop->TrafficLights; + PiTraffic->TrafficLights; + Pibrella->CompositeOutputDevice; + PingServer->PolledInternalDevice; + PolledInternalDevice->InternalDevice; + PololuDRV8835Robot->PhaseEnableRobot; + PumpkinPi->LEDBoard; + RGBLED->Device; + RGBLED->SourceMixin; + Robot->CompositeDevice; + Robot->SourceMixin; + RotaryEncoder->CompositeDevice; + RotaryEncoder->EventsMixin; + RyanteckRobot->Robot; + SPIDevice->Device; + Servo->CompositeDevice; + Servo->SourceMixin; + SmoothedInputDevice->EventsMixin; + SmoothedInputDevice->InputDevice; + SnowPi->LEDBoard; + StatusBoard->CompositeOutputDevice; + StatusZero->LEDBoard; + TimeOfDay->PolledInternalDevice; + TonalBuzzer->CompositeDevice; + TonalBuzzer->SourceMixin; + TrafficHat->CompositeOutputDevice; + TrafficLights->LEDBoard; + TrafficLightsBuzzer->CompositeOutputDevice; + TrafficpHat->TrafficLights; +} diff --git a/docs/images/device_hierarchy.pdf b/docs/images/device_hierarchy.pdf new file mode 100644 index 000000000..c79445edc Binary files /dev/null and b/docs/images/device_hierarchy.pdf differ diff --git a/docs/images/device_hierarchy.png b/docs/images/device_hierarchy.png new file mode 100644 index 000000000..92a446457 Binary files /dev/null and b/docs/images/device_hierarchy.png differ diff --git a/docs/images/device_hierarchy.svg b/docs/images/device_hierarchy.svg new file mode 100644 index 000000000..d86643bfe --- /dev/null +++ b/docs/images/device_hierarchy.svg @@ -0,0 +1,1105 @@ + + + + + + +classes + + + +EventsMixin + +EventsMixin + + + +HoldMixin + +HoldMixin + + + +HoldMixin->EventsMixin + + + + + +SharedMixin + +SharedMixin + + + +SourceMixin + +SourceMixin + + + +ValuesMixin + +ValuesMixin + + + +AnalogInputDevice + +AnalogInputDevice + + + +SPIDevice + +SPIDevice + + + +AnalogInputDevice->SPIDevice + + + + + +CompositeDevice + +CompositeDevice + + + +Device + +Device + + + +CompositeDevice->Device + + + + + +CompositeOutputDevice + +CompositeOutputDevice + + + +CompositeOutputDevice->SourceMixin + + + + + +CompositeOutputDevice->CompositeDevice + + + + + +Device->ValuesMixin + + + + + +GPIODevice + +GPIODevice + + + +GPIODevice->Device + + + + + +InternalDevice + +InternalDevice + + + +InternalDevice->EventsMixin + + + + + +InternalDevice->Device + + + + + +LEDCollection + +LEDCollection + + + +LEDCollection->CompositeOutputDevice + + + + + +MCP30xx + +MCP30xx + + + +MCP3xxx + +MCP3xxx + + + +MCP30xx->MCP3xxx + + + + + +MCP32xx + +MCP32xx + + + +MCP32xx->MCP3xxx + + + + + +MCP33xx + +MCP33xx + + + +MCP33xx->MCP3xxx + + + + + +MCP3xx2 + +MCP3xx2 + + + +MCP3xx2->MCP3xxx + + + + + +MCP3xxx->AnalogInputDevice + + + + + +SPIDevice->Device + + + + + +SmoothedInputDevice + +SmoothedInputDevice + + + +SmoothedInputDevice->EventsMixin + + + + + +InputDevice + +InputDevice + + + +SmoothedInputDevice->InputDevice + + + + + +AngularServo + +AngularServo + + + +Servo + +Servo + + + +AngularServo->Servo + + + + + +Servo->SourceMixin + + + + + +Servo->CompositeDevice + + + + + +Button + +Button + + + +Button->HoldMixin + + + + + +DigitalInputDevice + +DigitalInputDevice + + + +Button->DigitalInputDevice + + + + + +DigitalInputDevice->EventsMixin + + + + + +DigitalInputDevice->InputDevice + + + + + +ButtonBoard + +ButtonBoard + + + +ButtonBoard->HoldMixin + + + + + +ButtonBoard->CompositeDevice + + + + + +Buzzer + +Buzzer + + + +DigitalOutputDevice + +DigitalOutputDevice + + + +Buzzer->DigitalOutputDevice + + + + + +OutputDevice + +OutputDevice + + + +DigitalOutputDevice->OutputDevice + + + + + +CPUTemperature + +CPUTemperature + + + +PolledInternalDevice + +PolledInternalDevice + + + +CPUTemperature->PolledInternalDevice + + + + + +PolledInternalDevice->InternalDevice + + + + + +CamJamKitRobot + +CamJamKitRobot + + + +Robot + +Robot + + + +CamJamKitRobot->Robot + + + + + +Robot->SourceMixin + + + + + +Robot->CompositeDevice + + + + + +InputDevice->GPIODevice + + + + + +OutputDevice->SourceMixin + + + + + +OutputDevice->GPIODevice + + + + + +DiskUsage + +DiskUsage + + + +DiskUsage->PolledInternalDevice + + + + + +DistanceSensor + +DistanceSensor + + + +DistanceSensor->SmoothedInputDevice + + + + + +Energenie + +Energenie + + + +Energenie->SourceMixin + + + + + +Energenie->Device + + + + + +FishDish + +FishDish + + + +FishDish->CompositeOutputDevice + + + + + +JamHat + +JamHat + + + +JamHat->CompositeOutputDevice + + + + + +LED + +LED + + + +LED->DigitalOutputDevice + + + + + +LEDBarGraph + +LEDBarGraph + + + +LEDBarGraph->LEDCollection + + + + + +LEDBoard + +LEDBoard + + + +LEDBoard->LEDCollection + + + + + +LEDCharDisplay + +LEDCharDisplay + + + +LEDCharDisplay->LEDCollection + + + + + +LEDMultiCharDisplay + +LEDMultiCharDisplay + + + +LEDMultiCharDisplay->CompositeOutputDevice + + + + + +LedBorg + +LedBorg + + + +RGBLED + +RGBLED + + + +LedBorg->RGBLED + + + + + +RGBLED->SourceMixin + + + + + +RGBLED->Device + + + + + +LightSensor + +LightSensor + + + +LightSensor->SmoothedInputDevice + + + + + +LineSensor + +LineSensor + + + +LineSensor->SmoothedInputDevice + + + + + +LoadAverage + +LoadAverage + + + +LoadAverage->PolledInternalDevice + + + + + +MCP3001 + +MCP3001 + + + +MCP3001->MCP30xx + + + + + +MCP3002 + +MCP3002 + + + +MCP3002->MCP30xx + + + + + +MCP3002->MCP3xx2 + + + + + +MCP3004 + +MCP3004 + + + +MCP3004->MCP30xx + + + + + +MCP3008 + +MCP3008 + + + +MCP3008->MCP30xx + + + + + +MCP3201 + +MCP3201 + + + +MCP3201->MCP32xx + + + + + +MCP3202 + +MCP3202 + + + +MCP3202->MCP32xx + + + + + +MCP3202->MCP3xx2 + + + + + +MCP3204 + +MCP3204 + + + +MCP3204->MCP32xx + + + + + +MCP3208 + +MCP3208 + + + +MCP3208->MCP32xx + + + + + +MCP3301 + +MCP3301 + + + +MCP3301->MCP33xx + + + + + +MCP3302 + +MCP3302 + + + +MCP3302->MCP33xx + + + + + +MCP3304 + +MCP3304 + + + +MCP3304->MCP33xx + + + + + +MotionSensor + +MotionSensor + + + +MotionSensor->SmoothedInputDevice + + + + + +Motor + +Motor + + + +Motor->SourceMixin + + + + + +Motor->CompositeDevice + + + + + +PWMLED + +PWMLED + + + +PWMOutputDevice + +PWMOutputDevice + + + +PWMLED->PWMOutputDevice + + + + + +PWMOutputDevice->OutputDevice + + + + + +PhaseEnableMotor + +PhaseEnableMotor + + + +PhaseEnableMotor->SourceMixin + + + + + +PhaseEnableMotor->CompositeDevice + + + + + +PhaseEnableRobot + +PhaseEnableRobot + + + +PhaseEnableRobot->SourceMixin + + + + + +PhaseEnableRobot->CompositeDevice + + + + + +PiHutXmasTree + +PiHutXmasTree + + + +PiHutXmasTree->LEDBoard + + + + + +PiLiter + +PiLiter + + + +PiLiter->LEDBoard + + + + + +PiLiterBarGraph + +PiLiterBarGraph + + + +PiLiterBarGraph->LEDBarGraph + + + + + +PiStop + +PiStop + + + +TrafficLights + +TrafficLights + + + +PiStop->TrafficLights + + + + + +TrafficLights->LEDBoard + + + + + +PiTraffic + +PiTraffic + + + +PiTraffic->TrafficLights + + + + + +Pibrella + +Pibrella + + + +Pibrella->CompositeOutputDevice + + + + + +PingServer + +PingServer + + + +PingServer->PolledInternalDevice + + + + + +PololuDRV8835Robot + +PololuDRV8835Robot + + + +PololuDRV8835Robot->PhaseEnableRobot + + + + + +PumpkinPi + +PumpkinPi + + + +PumpkinPi->LEDBoard + + + + + +RotaryEncoder + +RotaryEncoder + + + +RotaryEncoder->EventsMixin + + + + + +RotaryEncoder->CompositeDevice + + + + + +RyanteckRobot + +RyanteckRobot + + + +RyanteckRobot->Robot + + + + + +SnowPi + +SnowPi + + + +SnowPi->LEDBoard + + + + + +StatusBoard + +StatusBoard + + + +StatusBoard->CompositeOutputDevice + + + + + +StatusZero + +StatusZero + + + +StatusZero->LEDBoard + + + + + +TimeOfDay + +TimeOfDay + + + +TimeOfDay->PolledInternalDevice + + + + + +TonalBuzzer + +TonalBuzzer + + + +TonalBuzzer->SourceMixin + + + + + +TonalBuzzer->CompositeDevice + + + + + +TrafficHat + +TrafficHat + + + +TrafficHat->CompositeOutputDevice + + + + + +TrafficLightsBuzzer + +TrafficLightsBuzzer + + + +TrafficLightsBuzzer->CompositeOutputDevice + + + + + +TrafficpHat + +TrafficpHat + + + +TrafficpHat->TrafficLights + + + + + diff --git a/docs/images/device_pin_flowchart.dot b/docs/images/device_pin_flowchart.dot new file mode 100644 index 000000000..230e0d23f --- /dev/null +++ b/docs/images/device_pin_flowchart.dot @@ -0,0 +1,24 @@ +/* vim: set et sw=4 sts=4: */ + +digraph device_pins { + graph [rankdir=TB]; + node [shape=rect, style=filled, color="#660033", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + edge [fontname=Arial, fontsize=10]; + + constructor [label="LED(pin_spec, ...,\npin_factory=None)"]; + pin_factory_kwarg [shape=diamond,label="pin_factory\nis None?"]; + device_pin_factory [shape=diamond,label="Device.pin_factory\nis None?"]; + set_default_factory [label="Device.pin_factory =\nDevice._default_pin_factory()"]; + get_default_factory [label="self.pin_factory =\nDevice.pin_factory"]; + override_factory [label="self.pin_factory =\npin_factory"]; + factory_pin [label="self.pin =\nself.pin_factory.pin(pin_spec)"]; + + constructor->pin_factory_kwarg; + pin_factory_kwarg->device_pin_factory [label="yes"]; + pin_factory_kwarg->override_factory [label="no"]; + device_pin_factory->set_default_factory [label="yes"]; + device_pin_factory->get_default_factory [label="no"]; + set_default_factory->get_default_factory; + get_default_factory->factory_pin; + override_factory->factory_pin; +} diff --git a/docs/images/device_pin_flowchart.pdf b/docs/images/device_pin_flowchart.pdf new file mode 100644 index 000000000..2e2197e52 Binary files /dev/null and b/docs/images/device_pin_flowchart.pdf differ diff --git a/docs/images/device_pin_flowchart.png b/docs/images/device_pin_flowchart.png new file mode 100644 index 000000000..4c36e8f60 Binary files /dev/null and b/docs/images/device_pin_flowchart.png differ diff --git a/docs/images/device_pin_flowchart.svg b/docs/images/device_pin_flowchart.svg new file mode 100644 index 000000000..7d121dca7 --- /dev/null +++ b/docs/images/device_pin_flowchart.svg @@ -0,0 +1,114 @@ + + + + + + +device_pins + + + +constructor + +LED(pin_spec, ..., +pin_factory=None) + + + +pin_factory_kwarg + +pin_factory +is None? + + + +constructor->pin_factory_kwarg + + + + + +device_pin_factory + +Device.pin_factory +is None? + + + +pin_factory_kwarg->device_pin_factory + + +yes + + + +override_factory + +self.pin_factory = +pin_factory + + + +pin_factory_kwarg->override_factory + + +no + + + +set_default_factory + +Device.pin_factory = +Device._default_pin_factory() + + + +device_pin_factory->set_default_factory + + +yes + + + +get_default_factory + +self.pin_factory = +Device.pin_factory + + + +device_pin_factory->get_default_factory + + +no + + + +set_default_factory->get_default_factory + + + + + +factory_pin + +self.pin = +self.pin_factory.pin(pin_spec) + + + +get_default_factory->factory_pin + + + + + +override_factory->factory_pin + + + + + diff --git a/docs/images/distance_sensor.fzz b/docs/images/distance_sensor.fzz new file mode 100644 index 000000000..b8d39816a Binary files /dev/null and b/docs/images/distance_sensor.fzz differ diff --git a/docs/images/distance_sensor_bb.pdf b/docs/images/distance_sensor_bb.pdf new file mode 100644 index 000000000..cf3637e48 Binary files /dev/null and b/docs/images/distance_sensor_bb.pdf differ diff --git a/docs/images/distance_sensor_bb.png b/docs/images/distance_sensor_bb.png new file mode 100644 index 000000000..8f408f1ca Binary files /dev/null and b/docs/images/distance_sensor_bb.png differ diff --git a/docs/images/distance_sensor_bb.svg b/docs/images/distance_sensor_bb.svg new file mode 100644 index 000000000..d31dbadb7 --- /dev/null +++ b/docs/images/distance_sensor_bb.svg @@ -0,0 +1,4702 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/gpio-expansion-example.png b/docs/images/gpio-expansion-example.png new file mode 100644 index 000000000..fd6dd8b21 Binary files /dev/null and b/docs/images/gpio-expansion-example.png differ diff --git a/docs/images/gpio-expansion-prompt.png b/docs/images/gpio-expansion-prompt.png new file mode 100644 index 000000000..9ccc5303c Binary files /dev/null and b/docs/images/gpio-expansion-prompt.png differ diff --git a/docs/images/input_device_hierarchy.dot b/docs/images/input_device_hierarchy.dot new file mode 100644 index 000000000..576c8385d --- /dev/null +++ b/docs/images/input_device_hierarchy.dot @@ -0,0 +1,28 @@ +digraph classes { + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + + + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + + GPIODevice; + SmoothedInputDevice; + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + + Button->DigitalInputDevice; + DigitalInputDevice->InputDevice; + DistanceSensor->SmoothedInputDevice; + InputDevice->GPIODevice; + LightSensor->SmoothedInputDevice; + LineSensor->SmoothedInputDevice; + MotionSensor->SmoothedInputDevice; + SmoothedInputDevice->InputDevice; +} diff --git a/docs/images/input_device_hierarchy.pdf b/docs/images/input_device_hierarchy.pdf new file mode 100644 index 000000000..bc5517e28 Binary files /dev/null and b/docs/images/input_device_hierarchy.pdf differ diff --git a/docs/images/input_device_hierarchy.png b/docs/images/input_device_hierarchy.png new file mode 100644 index 000000000..a432f0009 Binary files /dev/null and b/docs/images/input_device_hierarchy.png differ diff --git a/docs/images/input_device_hierarchy.svg b/docs/images/input_device_hierarchy.svg new file mode 100644 index 000000000..43dd9645e --- /dev/null +++ b/docs/images/input_device_hierarchy.svg @@ -0,0 +1,115 @@ + + + + + + +classes + + + +GPIODevice + +GPIODevice + + + +SmoothedInputDevice + +SmoothedInputDevice + + + +InputDevice + +InputDevice + + + +SmoothedInputDevice->InputDevice + + + + + +Button + +Button + + + +DigitalInputDevice + +DigitalInputDevice + + + +Button->DigitalInputDevice + + + + + +DigitalInputDevice->InputDevice + + + + + +InputDevice->GPIODevice + + + + + +DistanceSensor + +DistanceSensor + + + +DistanceSensor->SmoothedInputDevice + + + + + +LightSensor + +LightSensor + + + +LightSensor->SmoothedInputDevice + + + + + +LineSensor + +LineSensor + + + +LineSensor->SmoothedInputDevice + + + + + +MotionSensor + +MotionSensor + + + +MotionSensor->SmoothedInputDevice + + + + + diff --git a/docs/images/internal_device_hierarchy.dot b/docs/images/internal_device_hierarchy.dot new file mode 100644 index 000000000..9c321ddfe --- /dev/null +++ b/docs/images/internal_device_hierarchy.dot @@ -0,0 +1,27 @@ +digraph classes { + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + + + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + + Device; + InternalDevice; + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + + CPUTemperature->PolledInternalDevice; + DiskUsage->PolledInternalDevice; + InternalDevice->Device; + LoadAverage->PolledInternalDevice; + PingServer->PolledInternalDevice; + PolledInternalDevice->InternalDevice; + TimeOfDay->PolledInternalDevice; +} diff --git a/docs/images/internal_device_hierarchy.pdf b/docs/images/internal_device_hierarchy.pdf new file mode 100644 index 000000000..f33793bb6 Binary files /dev/null and b/docs/images/internal_device_hierarchy.pdf differ diff --git a/docs/images/internal_device_hierarchy.png b/docs/images/internal_device_hierarchy.png new file mode 100644 index 000000000..bf12954ad Binary files /dev/null and b/docs/images/internal_device_hierarchy.png differ diff --git a/docs/images/internal_device_hierarchy.svg b/docs/images/internal_device_hierarchy.svg new file mode 100644 index 000000000..a1b705c7d --- /dev/null +++ b/docs/images/internal_device_hierarchy.svg @@ -0,0 +1,103 @@ + + + + + + +classes + + + +Device + +Device + + + +InternalDevice + +InternalDevice + + + +InternalDevice->Device + + + + + +CPUTemperature + +CPUTemperature + + + +PolledInternalDevice + +PolledInternalDevice + + + +CPUTemperature->PolledInternalDevice + + + + + +PolledInternalDevice->InternalDevice + + + + + +DiskUsage + +DiskUsage + + + +DiskUsage->PolledInternalDevice + + + + + +LoadAverage + +LoadAverage + + + +LoadAverage->PolledInternalDevice + + + + + +PingServer + +PingServer + + + +PingServer->PolledInternalDevice + + + + + +TimeOfDay + +TimeOfDay + + + +TimeOfDay->PolledInternalDevice + + + + + diff --git a/docs/images/led.fzz b/docs/images/led.fzz new file mode 100644 index 000000000..51e514f76 Binary files /dev/null and b/docs/images/led.fzz differ diff --git a/docs/images/led_bb.pdf b/docs/images/led_bb.pdf new file mode 100644 index 000000000..531d838f9 Binary files /dev/null and b/docs/images/led_bb.pdf differ diff --git a/docs/images/led_bb.png b/docs/images/led_bb.png new file mode 100644 index 000000000..6163dff55 Binary files /dev/null and b/docs/images/led_bb.png differ diff --git a/docs/images/led_bb.svg b/docs/images/led_bb.svg new file mode 100644 index 000000000..9114cb325 --- /dev/null +++ b/docs/images/led_bb.svg @@ -0,0 +1,1622 @@ + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/led_button.fzz b/docs/images/led_button.fzz new file mode 100644 index 000000000..0b1d77584 Binary files /dev/null and b/docs/images/led_button.fzz differ diff --git a/docs/images/led_button_bb.pdf b/docs/images/led_button_bb.pdf new file mode 100644 index 000000000..145e2a185 Binary files /dev/null and b/docs/images/led_button_bb.pdf differ diff --git a/docs/images/led_button_bb.png b/docs/images/led_button_bb.png new file mode 100644 index 000000000..f47938267 Binary files /dev/null and b/docs/images/led_button_bb.png differ diff --git a/docs/images/led_button_bb.svg b/docs/images/led_button_bb.svg new file mode 100644 index 000000000..d4e6e1aca --- /dev/null +++ b/docs/images/led_button_bb.svg @@ -0,0 +1,4562 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/led_char_display.fzz b/docs/images/led_char_display.fzz new file mode 100644 index 000000000..b41649ad1 Binary files /dev/null and b/docs/images/led_char_display.fzz differ diff --git a/docs/images/led_char_display_bb.pdf b/docs/images/led_char_display_bb.pdf new file mode 100644 index 000000000..ff5266f1b Binary files /dev/null and b/docs/images/led_char_display_bb.pdf differ diff --git a/docs/images/led_char_display_bb.png b/docs/images/led_char_display_bb.png new file mode 100644 index 000000000..3916850dc Binary files /dev/null and b/docs/images/led_char_display_bb.png differ diff --git a/docs/images/led_char_display_bb.svg b/docs/images/led_char_display_bb.svg new file mode 100644 index 000000000..2b3fbeeac --- /dev/null +++ b/docs/images/led_char_display_bb.svg @@ -0,0 +1,4682 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/ledbargraph.fzz b/docs/images/ledbargraph.fzz new file mode 100644 index 000000000..912859d92 Binary files /dev/null and b/docs/images/ledbargraph.fzz differ diff --git a/docs/images/ledbargraph_bb.pdf b/docs/images/ledbargraph_bb.pdf new file mode 100644 index 000000000..c34f9cfe6 Binary files /dev/null and b/docs/images/ledbargraph_bb.pdf differ diff --git a/docs/images/ledbargraph_bb.png b/docs/images/ledbargraph_bb.png new file mode 100644 index 000000000..5570c96cd Binary files /dev/null and b/docs/images/ledbargraph_bb.png differ diff --git a/docs/images/ledbargraph_bb.svg b/docs/images/ledbargraph_bb.svg new file mode 100644 index 000000000..fa4079ff0 --- /dev/null +++ b/docs/images/ledbargraph_bb.svg @@ -0,0 +1,4760 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/ledboard.fzz b/docs/images/ledboard.fzz new file mode 100644 index 000000000..a944657a9 Binary files /dev/null and b/docs/images/ledboard.fzz differ diff --git a/docs/images/ledboard_bb.pdf b/docs/images/ledboard_bb.pdf new file mode 100644 index 000000000..ce8e1019f Binary files /dev/null and b/docs/images/ledboard_bb.pdf differ diff --git a/docs/images/ledboard_bb.png b/docs/images/ledboard_bb.png new file mode 100644 index 000000000..46026861d Binary files /dev/null and b/docs/images/ledboard_bb.png differ diff --git a/docs/images/ledboard_bb.svg b/docs/images/ledboard_bb.svg new file mode 100644 index 000000000..866269c20 --- /dev/null +++ b/docs/images/ledboard_bb.svg @@ -0,0 +1,4715 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/light_sensor.fzz b/docs/images/light_sensor.fzz new file mode 100644 index 000000000..088bdeaff Binary files /dev/null and b/docs/images/light_sensor.fzz differ diff --git a/docs/images/light_sensor_bb.pdf b/docs/images/light_sensor_bb.pdf new file mode 100644 index 000000000..9735c72f8 Binary files /dev/null and b/docs/images/light_sensor_bb.pdf differ diff --git a/docs/images/light_sensor_bb.png b/docs/images/light_sensor_bb.png new file mode 100644 index 000000000..9d154d458 Binary files /dev/null and b/docs/images/light_sensor_bb.png differ diff --git a/docs/images/light_sensor_bb.svg b/docs/images/light_sensor_bb.svg new file mode 100644 index 000000000..7db99e0d2 --- /dev/null +++ b/docs/images/light_sensor_bb.svg @@ -0,0 +1,4588 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/motion_robot.fzz b/docs/images/motion_robot.fzz new file mode 100644 index 000000000..910fb972c Binary files /dev/null and b/docs/images/motion_robot.fzz differ diff --git a/docs/images/motion_robot_bb.pdf b/docs/images/motion_robot_bb.pdf new file mode 100644 index 000000000..13a99324f Binary files /dev/null and b/docs/images/motion_robot_bb.pdf differ diff --git a/docs/images/motion_robot_bb.png b/docs/images/motion_robot_bb.png new file mode 100644 index 000000000..f30e784d4 Binary files /dev/null and b/docs/images/motion_robot_bb.png differ diff --git a/docs/images/motion_robot_bb.svg b/docs/images/motion_robot_bb.svg new file mode 100644 index 000000000..334fafa1b --- /dev/null +++ b/docs/images/motion_robot_bb.svg @@ -0,0 +1,4676 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/motion_sensor.fzz b/docs/images/motion_sensor.fzz new file mode 100644 index 000000000..2b89afdcb Binary files /dev/null and b/docs/images/motion_sensor.fzz differ diff --git a/docs/images/motion_sensor_bb.pdf b/docs/images/motion_sensor_bb.pdf new file mode 100644 index 000000000..47344e420 Binary files /dev/null and b/docs/images/motion_sensor_bb.pdf differ diff --git a/docs/images/motion_sensor_bb.png b/docs/images/motion_sensor_bb.png new file mode 100644 index 000000000..5d070d65f Binary files /dev/null and b/docs/images/motion_sensor_bb.png differ diff --git a/docs/images/motion_sensor_bb.svg b/docs/images/motion_sensor_bb.svg new file mode 100644 index 000000000..a5c2ace5d --- /dev/null +++ b/docs/images/motion_sensor_bb.svg @@ -0,0 +1,4564 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/motor.fzz b/docs/images/motor.fzz new file mode 100644 index 000000000..5d1930fe7 Binary files /dev/null and b/docs/images/motor.fzz differ diff --git a/docs/images/motor_bb.pdf b/docs/images/motor_bb.pdf new file mode 100644 index 000000000..2b443e986 Binary files /dev/null and b/docs/images/motor_bb.pdf differ diff --git a/docs/images/motor_bb.png b/docs/images/motor_bb.png new file mode 100644 index 000000000..195cbc053 Binary files /dev/null and b/docs/images/motor_bb.png differ diff --git a/docs/images/motor_bb.svg b/docs/images/motor_bb.svg new file mode 100644 index 000000000..02a9a8151 --- /dev/null +++ b/docs/images/motor_bb.svg @@ -0,0 +1,4596 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/music_box.fzz b/docs/images/music_box.fzz new file mode 100644 index 000000000..a19249407 Binary files /dev/null and b/docs/images/music_box.fzz differ diff --git a/docs/images/music_box_bb.pdf b/docs/images/music_box_bb.pdf new file mode 100644 index 000000000..a3bec0052 Binary files /dev/null and b/docs/images/music_box_bb.pdf differ diff --git a/docs/images/music_box_bb.png b/docs/images/music_box_bb.png new file mode 100644 index 000000000..a3f225359 Binary files /dev/null and b/docs/images/music_box_bb.png differ diff --git a/docs/images/music_box_bb.svg b/docs/images/music_box_bb.svg new file mode 100644 index 000000000..15c6abe1f --- /dev/null +++ b/docs/images/music_box_bb.svg @@ -0,0 +1,4544 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/output_device_hierarchy.dot b/docs/images/output_device_hierarchy.dot new file mode 100644 index 000000000..d84d4afd3 --- /dev/null +++ b/docs/images/output_device_hierarchy.dot @@ -0,0 +1,35 @@ +digraph classes { + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + + + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + + CompositeDevice; + Device; + GPIODevice; + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + + AngularServo->Servo; + Buzzer->DigitalOutputDevice; + CompositeDevice->Device; + DigitalOutputDevice->OutputDevice; + GPIODevice->Device; + LED->DigitalOutputDevice; + Motor->CompositeDevice; + OutputDevice->GPIODevice; + PWMLED->PWMOutputDevice; + PWMOutputDevice->OutputDevice; + PhaseEnableMotor->CompositeDevice; + RGBLED->Device; + Servo->CompositeDevice; + TonalBuzzer->CompositeDevice; +} diff --git a/docs/images/output_device_hierarchy.pdf b/docs/images/output_device_hierarchy.pdf new file mode 100644 index 000000000..2cc69958e Binary files /dev/null and b/docs/images/output_device_hierarchy.pdf differ diff --git a/docs/images/output_device_hierarchy.png b/docs/images/output_device_hierarchy.png new file mode 100644 index 000000000..8bed262a8 Binary files /dev/null and b/docs/images/output_device_hierarchy.png differ diff --git a/docs/images/output_device_hierarchy.svg b/docs/images/output_device_hierarchy.svg new file mode 100644 index 000000000..148bb38ed --- /dev/null +++ b/docs/images/output_device_hierarchy.svg @@ -0,0 +1,187 @@ + + + + + + +classes + + + +CompositeDevice + +CompositeDevice + + + +Device + +Device + + + +CompositeDevice->Device + + + + + +GPIODevice + +GPIODevice + + + +GPIODevice->Device + + + + + +AngularServo + +AngularServo + + + +Servo + +Servo + + + +AngularServo->Servo + + + + + +Servo->CompositeDevice + + + + + +Buzzer + +Buzzer + + + +DigitalOutputDevice + +DigitalOutputDevice + + + +Buzzer->DigitalOutputDevice + + + + + +OutputDevice + +OutputDevice + + + +DigitalOutputDevice->OutputDevice + + + + + +OutputDevice->GPIODevice + + + + + +LED + +LED + + + +LED->DigitalOutputDevice + + + + + +Motor + +Motor + + + +Motor->CompositeDevice + + + + + +PWMLED + +PWMLED + + + +PWMOutputDevice + +PWMOutputDevice + + + +PWMLED->PWMOutputDevice + + + + + +PWMOutputDevice->OutputDevice + + + + + +PhaseEnableMotor + +PhaseEnableMotor + + + +PhaseEnableMotor->CompositeDevice + + + + + +RGBLED + +RGBLED + + + +RGBLED->Device + + + + + +TonalBuzzer + +TonalBuzzer + + + +TonalBuzzer->CompositeDevice + + + + + diff --git a/docs/images/pin_layout.pdf b/docs/images/pin_layout.pdf new file mode 100644 index 000000000..18aa5d008 Binary files /dev/null and b/docs/images/pin_layout.pdf differ diff --git a/docs/images/pin_layout.png b/docs/images/pin_layout.png new file mode 100644 index 000000000..96dc06ffc Binary files /dev/null and b/docs/images/pin_layout.png differ diff --git a/docs/images/pin_layout.svg b/docs/images/pin_layout.svg new file mode 100644 index 000000000..84e0ff2b8 --- /dev/null +++ b/docs/images/pin_layout.svg @@ -0,0 +1,2777 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + 3V3Power + + + + GPIO2SDA I²C + + + + GPIO3SCL I²C + + + + GPIO4 + + + + Ground + + + + GPIO17 + + + + GPIO27 + + + + GPIO22 + + + + 3V3Power + + + + GPIO10SPI MOSI + + + + GPIO9SPI MISO + + + + GPIO11SPI SCLK + + + Ground + + + + ID SDI²C ID + + + + GPIO5 + + + + GPIO6 + + + + GPIO13 + + + + GPIO19 + + + + GPIO26 + + + + Ground + + + + 5VPower + + + + 5VPower + + + + Ground + + + + GPIO14UART0 TXD + + + + GPIO15UART0 RXD + + + + GPIO18 + + + + Ground + + + + GPIO23 + + + + GPIO24 + + + + Ground + + + + GPIO25 + + + + GPIO8SPI CE0 + + + + GPIO7SPI CE1 + + + + ID SCI²C ID + + + + Ground + + + + GPIO12 + + + + Ground + + + + GPIO16 + + + + GPIO20 + + + + GPIO21 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + All Models + 40-pinmodels only + 1 + 1 + 3 + 5 + 7 + 9 + 11 + 13 + 15 + 17 + 19 + 21 + 23 + 25 + 27 + 29 + 31 + 33 + 35 + 37 + 39 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + 18 + 20 + 22 + 24 + 26 + 28 + 30 + 32 + 34 + 36 + 38 + 40 + USB Ports + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/pinout_pi3.png b/docs/images/pinout_pi3.png new file mode 100644 index 000000000..6ed73bda3 Binary files /dev/null and b/docs/images/pinout_pi3.png differ diff --git a/docs/images/pinout_pizero_w.png b/docs/images/pinout_pizero_w.png new file mode 100644 index 000000000..5c76128ed Binary files /dev/null and b/docs/images/pinout_pizero_w.png differ diff --git a/docs/images/pintest.png b/docs/images/pintest.png new file mode 100644 index 000000000..ee2319070 Binary files /dev/null and b/docs/images/pintest.png differ diff --git a/docs/images/potentiometer.fzz b/docs/images/potentiometer.fzz new file mode 100644 index 000000000..cb0b422e0 Binary files /dev/null and b/docs/images/potentiometer.fzz differ diff --git a/docs/images/potentiometer_bb.pdf b/docs/images/potentiometer_bb.pdf new file mode 100644 index 000000000..a4bf38c14 Binary files /dev/null and b/docs/images/potentiometer_bb.pdf differ diff --git a/docs/images/potentiometer_bb.png b/docs/images/potentiometer_bb.png new file mode 100644 index 000000000..baf0b893c Binary files /dev/null and b/docs/images/potentiometer_bb.png differ diff --git a/docs/images/potentiometer_bb.svg b/docs/images/potentiometer_bb.svg new file mode 100644 index 000000000..93e52435d --- /dev/null +++ b/docs/images/potentiometer_bb.svg @@ -0,0 +1,5030 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +MCP3008 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/raspi-config.png b/docs/images/raspi-config.png new file mode 100644 index 000000000..d319bf188 Binary files /dev/null and b/docs/images/raspi-config.png differ diff --git a/docs/images/reaction_game.fzz b/docs/images/reaction_game.fzz new file mode 100644 index 000000000..2f215a304 Binary files /dev/null and b/docs/images/reaction_game.fzz differ diff --git a/docs/images/reaction_game_bb.pdf b/docs/images/reaction_game_bb.pdf new file mode 100644 index 000000000..957862d8d Binary files /dev/null and b/docs/images/reaction_game_bb.pdf differ diff --git a/docs/images/reaction_game_bb.png b/docs/images/reaction_game_bb.png new file mode 100644 index 000000000..faa1a0321 Binary files /dev/null and b/docs/images/reaction_game_bb.png differ diff --git a/docs/images/reaction_game_bb.svg b/docs/images/reaction_game_bb.svg new file mode 100644 index 000000000..bd5bb1cf9 --- /dev/null +++ b/docs/images/reaction_game_bb.svg @@ -0,0 +1,4589 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/rgb_led.fzz b/docs/images/rgb_led.fzz new file mode 100644 index 000000000..883a35b00 Binary files /dev/null and b/docs/images/rgb_led.fzz differ diff --git a/docs/images/rgb_led_bb.pdf b/docs/images/rgb_led_bb.pdf new file mode 100644 index 000000000..d2b4ca5fa Binary files /dev/null and b/docs/images/rgb_led_bb.pdf differ diff --git a/docs/images/rgb_led_bb.png b/docs/images/rgb_led_bb.png new file mode 100644 index 000000000..13fd43826 Binary files /dev/null and b/docs/images/rgb_led_bb.png differ diff --git a/docs/images/rgb_led_bb.svg b/docs/images/rgb_led_bb.svg new file mode 100644 index 000000000..742da7883 --- /dev/null +++ b/docs/images/rgb_led_bb.svg @@ -0,0 +1,4579 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/rgbled_pot.fzz b/docs/images/rgbled_pot.fzz new file mode 100644 index 000000000..de7575a00 Binary files /dev/null and b/docs/images/rgbled_pot.fzz differ diff --git a/docs/images/rgbled_pot_bb.pdf b/docs/images/rgbled_pot_bb.pdf new file mode 100644 index 000000000..fe7cb6b60 Binary files /dev/null and b/docs/images/rgbled_pot_bb.pdf differ diff --git a/docs/images/rgbled_pot_bb.png b/docs/images/rgbled_pot_bb.png new file mode 100644 index 000000000..e09aecba6 Binary files /dev/null and b/docs/images/rgbled_pot_bb.png differ diff --git a/docs/images/rgbled_pot_bb.svg b/docs/images/rgbled_pot_bb.svg new file mode 100644 index 000000000..ee3035ca1 --- /dev/null +++ b/docs/images/rgbled_pot_bb.svg @@ -0,0 +1,4766 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +MCP3008 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/robot.fzz b/docs/images/robot.fzz new file mode 100644 index 000000000..3e543adb0 Binary files /dev/null and b/docs/images/robot.fzz differ diff --git a/docs/images/robot_bb.pdf b/docs/images/robot_bb.pdf new file mode 100644 index 000000000..49a321472 Binary files /dev/null and b/docs/images/robot_bb.pdf differ diff --git a/docs/images/robot_bb.png b/docs/images/robot_bb.png new file mode 100644 index 000000000..d2c4bed29 Binary files /dev/null and b/docs/images/robot_bb.png differ diff --git a/docs/images/robot_bb.svg b/docs/images/robot_bb.svg new file mode 100644 index 000000000..f4df63dc1 --- /dev/null +++ b/docs/images/robot_bb.svg @@ -0,0 +1,4647 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/images/source_values/combining_sources.dot b/docs/images/source_values/combining_sources.dot new file mode 100644 index 000000000..044defd54 --- /dev/null +++ b/docs/images/source_values/combining_sources.dot @@ -0,0 +1,22 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + output [label="Output device"] + input_1 [label="Input device 1"] + input_2 [label="Input device 2"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + tool [label="source tool"] + + input_1 -> tool; + input_2 -> tool; + tool -> output; +} diff --git a/docs/images/source_values/combining_sources.pdf b/docs/images/source_values/combining_sources.pdf new file mode 100644 index 000000000..98be67081 Binary files /dev/null and b/docs/images/source_values/combining_sources.pdf differ diff --git a/docs/images/source_values/combining_sources.png b/docs/images/source_values/combining_sources.png new file mode 100644 index 000000000..7cb24ff38 Binary files /dev/null and b/docs/images/source_values/combining_sources.png differ diff --git a/docs/images/source_values/combining_sources.svg b/docs/images/source_values/combining_sources.svg new file mode 100644 index 000000000..9045e5d4a --- /dev/null +++ b/docs/images/source_values/combining_sources.svg @@ -0,0 +1,48 @@ + + + + + + +%3 + + +output + +Output device + + +input_1 + +Input device 1 + + +tool + +source tool + + +input_1->tool + + + + +input_2 + +Input device 2 + + +input_2->tool + + + + +tool->output + + + + + diff --git a/docs/images/source_values/combining_sources_led_buttons.dot b/docs/images/source_values/combining_sources_led_buttons.dot new file mode 100644 index 000000000..84d5a55fc --- /dev/null +++ b/docs/images/source_values/combining_sources_led_buttons.dot @@ -0,0 +1,22 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + LED + button_a [label="Button A"] + button_b [label="Button B"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + all_values + + button_a -> all_values; + button_b -> all_values; + all_values -> LED; +} diff --git a/docs/images/source_values/combining_sources_led_buttons.pdf b/docs/images/source_values/combining_sources_led_buttons.pdf new file mode 100644 index 000000000..d63dd4993 Binary files /dev/null and b/docs/images/source_values/combining_sources_led_buttons.pdf differ diff --git a/docs/images/source_values/combining_sources_led_buttons.png b/docs/images/source_values/combining_sources_led_buttons.png new file mode 100644 index 000000000..47682bf2a Binary files /dev/null and b/docs/images/source_values/combining_sources_led_buttons.png differ diff --git a/docs/images/source_values/combining_sources_led_buttons.svg b/docs/images/source_values/combining_sources_led_buttons.svg new file mode 100644 index 000000000..829cdc55e --- /dev/null +++ b/docs/images/source_values/combining_sources_led_buttons.svg @@ -0,0 +1,48 @@ + + + + + + +%3 + + +LED + +LED + + +button_a + +Button A + + +all_values + +all_values + + +button_a->all_values + + + + +button_b + +Button B + + +button_b->all_values + + + + +all_values->LED + + + + + diff --git a/docs/images/source_values/custom_generator.dot b/docs/images/source_values/custom_generator.dot new file mode 100644 index 000000000..7d5015329 --- /dev/null +++ b/docs/images/source_values/custom_generator.dot @@ -0,0 +1,18 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + output [label="Output device"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + gen [label="custom generator"] + + gen -> output; +} diff --git a/docs/images/source_values/custom_generator.pdf b/docs/images/source_values/custom_generator.pdf new file mode 100644 index 000000000..22042d4ea Binary files /dev/null and b/docs/images/source_values/custom_generator.pdf differ diff --git a/docs/images/source_values/custom_generator.png b/docs/images/source_values/custom_generator.png new file mode 100644 index 000000000..21d7ce919 Binary files /dev/null and b/docs/images/source_values/custom_generator.png differ diff --git a/docs/images/source_values/custom_generator.svg b/docs/images/source_values/custom_generator.svg new file mode 100644 index 000000000..04fa6e51a --- /dev/null +++ b/docs/images/source_values/custom_generator.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +output + +Output device + + +gen + +custom generator + + +gen->output + + + + + diff --git a/docs/images/source_values/disk_usage_bar_graph.dot b/docs/images/source_values/disk_usage_bar_graph.dot new file mode 100644 index 000000000..1479cdbab --- /dev/null +++ b/docs/images/source_values/disk_usage_bar_graph.dot @@ -0,0 +1,14 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + du [label="Disk usage"] + bargraph [label="LED bar graph"] + + du -> bargraph; +} diff --git a/docs/images/source_values/disk_usage_bar_graph.pdf b/docs/images/source_values/disk_usage_bar_graph.pdf new file mode 100644 index 000000000..1c45dcd00 Binary files /dev/null and b/docs/images/source_values/disk_usage_bar_graph.pdf differ diff --git a/docs/images/source_values/disk_usage_bar_graph.png b/docs/images/source_values/disk_usage_bar_graph.png new file mode 100644 index 000000000..e7da9a967 Binary files /dev/null and b/docs/images/source_values/disk_usage_bar_graph.png differ diff --git a/docs/images/source_values/disk_usage_bar_graph.svg b/docs/images/source_values/disk_usage_bar_graph.svg new file mode 100644 index 000000000..63022ae8f --- /dev/null +++ b/docs/images/source_values/disk_usage_bar_graph.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +du + +Disk usage + + +bargraph + +LED bar graph + + +du->bargraph + + + + + diff --git a/docs/images/source_values/garden_light.dot b/docs/images/source_values/garden_light.dot new file mode 100644 index 000000000..ad3ff2dd9 --- /dev/null +++ b/docs/images/source_values/garden_light.dot @@ -0,0 +1,24 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + led [label="Garden light"] + light [label="Light sensor"] + motion [label="Motion sensor"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + booleanized + all_values + + all_values -> led; + booleanized -> all_values; + motion -> all_values; + light -> booleanized; +} diff --git a/docs/images/source_values/garden_light.pdf b/docs/images/source_values/garden_light.pdf new file mode 100644 index 000000000..34da2251a Binary files /dev/null and b/docs/images/source_values/garden_light.pdf differ diff --git a/docs/images/source_values/garden_light.png b/docs/images/source_values/garden_light.png new file mode 100644 index 000000000..01899706d Binary files /dev/null and b/docs/images/source_values/garden_light.png differ diff --git a/docs/images/source_values/garden_light.svg b/docs/images/source_values/garden_light.svg new file mode 100644 index 000000000..7ba290d63 --- /dev/null +++ b/docs/images/source_values/garden_light.svg @@ -0,0 +1,58 @@ + + + + + + +%3 + + +led + +Garden light + + +light + +Light sensor + + +booleanized + +booleanized + + +light->booleanized + + + + +motion + +Motion sensor + + +all_values + +all_values + + +motion->all_values + + + + +booleanized->all_values + + + + +all_values->led + + + + + diff --git a/docs/images/source_values/led_button.dot b/docs/images/source_values/led_button.dot new file mode 100644 index 000000000..55a496085 --- /dev/null +++ b/docs/images/source_values/led_button.dot @@ -0,0 +1,9 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + edge [arrowhead=normal, style=solid]; + + Button -> LED; +} diff --git a/docs/images/source_values/led_button.pdf b/docs/images/source_values/led_button.pdf new file mode 100644 index 000000000..71cc05968 Binary files /dev/null and b/docs/images/source_values/led_button.pdf differ diff --git a/docs/images/source_values/led_button.png b/docs/images/source_values/led_button.png new file mode 100644 index 000000000..f6dadded9 Binary files /dev/null and b/docs/images/source_values/led_button.png differ diff --git a/docs/images/source_values/led_button.svg b/docs/images/source_values/led_button.svg new file mode 100644 index 000000000..2ba1c7bbd --- /dev/null +++ b/docs/images/source_values/led_button.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +Button + +Button + + +LED + +LED + + +Button->LED + + + + + diff --git a/docs/images/source_values/led_button_negated.dot b/docs/images/source_values/led_button_negated.dot new file mode 100644 index 000000000..678f4f943 --- /dev/null +++ b/docs/images/source_values/led_button_negated.dot @@ -0,0 +1,19 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + LED + Button + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + negated + + Button -> negated -> LED; +} diff --git a/docs/images/source_values/led_button_negated.pdf b/docs/images/source_values/led_button_negated.pdf new file mode 100644 index 000000000..84675047d Binary files /dev/null and b/docs/images/source_values/led_button_negated.pdf differ diff --git a/docs/images/source_values/led_button_negated.png b/docs/images/source_values/led_button_negated.png new file mode 100644 index 000000000..2f4402dab Binary files /dev/null and b/docs/images/source_values/led_button_negated.png differ diff --git a/docs/images/source_values/led_button_negated.svg b/docs/images/source_values/led_button_negated.svg new file mode 100644 index 000000000..4cd267d5f --- /dev/null +++ b/docs/images/source_values/led_button_negated.svg @@ -0,0 +1,38 @@ + + + + + + +%3 + + +LED + +LED + + +Button + +Button + + +negated + +negated + + +Button->negated + + + + +negated->LED + + + + + diff --git a/docs/images/source_values/led_button_opposite.dot b/docs/images/source_values/led_button_opposite.dot new file mode 100644 index 000000000..7eaddcc01 --- /dev/null +++ b/docs/images/source_values/led_button_opposite.dot @@ -0,0 +1,19 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + LED + Button + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + opposite + + Button -> opposite -> LED; +} diff --git a/docs/images/source_values/led_button_opposite.pdf b/docs/images/source_values/led_button_opposite.pdf new file mode 100644 index 000000000..69ad1b92e Binary files /dev/null and b/docs/images/source_values/led_button_opposite.pdf differ diff --git a/docs/images/source_values/led_button_opposite.png b/docs/images/source_values/led_button_opposite.png new file mode 100644 index 000000000..54a2be663 Binary files /dev/null and b/docs/images/source_values/led_button_opposite.png differ diff --git a/docs/images/source_values/led_button_opposite.svg b/docs/images/source_values/led_button_opposite.svg new file mode 100644 index 000000000..5095ea981 --- /dev/null +++ b/docs/images/source_values/led_button_opposite.svg @@ -0,0 +1,38 @@ + + + + + + +%3 + + +LED + +LED + + +Button + +Button + + +opposite + +opposite + + +Button->opposite + + + + +opposite->LED + + + + + diff --git a/docs/images/source_values/matching_leds.dot b/docs/images/source_values/matching_leds.dot new file mode 100644 index 000000000..8077b4003 --- /dev/null +++ b/docs/images/source_values/matching_leds.dot @@ -0,0 +1,13 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + edge [arrowhead=normal, style=solid]; + + red [label="Red LED"] + green [label="Green LED"] + + Button -> red; + red -> green; +} diff --git a/docs/images/source_values/matching_leds.pdf b/docs/images/source_values/matching_leds.pdf new file mode 100644 index 000000000..9b81a0e35 Binary files /dev/null and b/docs/images/source_values/matching_leds.pdf differ diff --git a/docs/images/source_values/matching_leds.png b/docs/images/source_values/matching_leds.png new file mode 100644 index 000000000..958d54d7a Binary files /dev/null and b/docs/images/source_values/matching_leds.png differ diff --git a/docs/images/source_values/matching_leds.svg b/docs/images/source_values/matching_leds.svg new file mode 100644 index 000000000..afa791745 --- /dev/null +++ b/docs/images/source_values/matching_leds.svg @@ -0,0 +1,38 @@ + + + + + + +%3 + + +red + +Red LED + + +green + +Green LED + + +red->green + + + + +Button + +Button + + +Button->red + + + + + diff --git a/docs/images/source_values/pwmled_pot.dot b/docs/images/source_values/pwmled_pot.dot new file mode 100644 index 000000000..496b8bb0a --- /dev/null +++ b/docs/images/source_values/pwmled_pot.dot @@ -0,0 +1,11 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + edge [arrowhead=normal, style=solid]; + + pwmled [label="PWM LED"] + + Potentiometer -> pwmled; +} diff --git a/docs/images/source_values/pwmled_pot.pdf b/docs/images/source_values/pwmled_pot.pdf new file mode 100644 index 000000000..fa9c284bb Binary files /dev/null and b/docs/images/source_values/pwmled_pot.pdf differ diff --git a/docs/images/source_values/pwmled_pot.png b/docs/images/source_values/pwmled_pot.png new file mode 100644 index 000000000..fcbad9daa Binary files /dev/null and b/docs/images/source_values/pwmled_pot.png differ diff --git a/docs/images/source_values/pwmled_pot.svg b/docs/images/source_values/pwmled_pot.svg new file mode 100644 index 000000000..b2f465248 --- /dev/null +++ b/docs/images/source_values/pwmled_pot.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +pwmled + +PWM LED + + +Potentiometer + +Potentiometer + + +Potentiometer->pwmled + + + + + diff --git a/docs/images/source_values/random_led.dot b/docs/images/source_values/random_led.dot new file mode 100644 index 000000000..d2bdf2ac6 --- /dev/null +++ b/docs/images/source_values/random_led.dot @@ -0,0 +1,18 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + LED + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + rand + + rand -> LED; +} diff --git a/docs/images/source_values/random_led.pdf b/docs/images/source_values/random_led.pdf new file mode 100644 index 000000000..1489e49ab Binary files /dev/null and b/docs/images/source_values/random_led.pdf differ diff --git a/docs/images/source_values/random_led.png b/docs/images/source_values/random_led.png new file mode 100644 index 000000000..163bfd31f Binary files /dev/null and b/docs/images/source_values/random_led.png differ diff --git a/docs/images/source_values/random_led.svg b/docs/images/source_values/random_led.svg new file mode 100644 index 000000000..a5fc6486e --- /dev/null +++ b/docs/images/source_values/random_led.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +LED + +LED + + +rand + +rand + + +rand->LED + + + + + diff --git a/docs/images/source_values/robot_pots_1.dot b/docs/images/source_values/robot_pots_1.dot new file mode 100644 index 000000000..ce2d6f65e --- /dev/null +++ b/docs/images/source_values/robot_pots_1.dot @@ -0,0 +1,22 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + Robot + left [label="Left potentiometer"] + right [label="Right potentiometer"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + zip_values + + left -> zip_values; + right -> zip_values; + zip_values -> Robot; +} diff --git a/docs/images/source_values/robot_pots_1.pdf b/docs/images/source_values/robot_pots_1.pdf new file mode 100644 index 000000000..f82b133f6 Binary files /dev/null and b/docs/images/source_values/robot_pots_1.pdf differ diff --git a/docs/images/source_values/robot_pots_1.png b/docs/images/source_values/robot_pots_1.png new file mode 100644 index 000000000..ca6712063 Binary files /dev/null and b/docs/images/source_values/robot_pots_1.png differ diff --git a/docs/images/source_values/robot_pots_1.svg b/docs/images/source_values/robot_pots_1.svg new file mode 100644 index 000000000..7d292ef40 --- /dev/null +++ b/docs/images/source_values/robot_pots_1.svg @@ -0,0 +1,48 @@ + + + + + + +%3 + + +Robot + +Robot + + +left + +Left potentiometer + + +zip_values + +zip_values + + +left->zip_values + + + + +right + +Right potentiometer + + +right->zip_values + + + + +zip_values->Robot + + + + + diff --git a/docs/images/source_values/robot_pots_2.dot b/docs/images/source_values/robot_pots_2.dot new file mode 100644 index 000000000..ff742744d --- /dev/null +++ b/docs/images/source_values/robot_pots_2.dot @@ -0,0 +1,26 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + Robot + left [label="Left potentiometer"] + right [label="Right potentiometer"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + zip + scaled_1 [label="scaled"] + scaled_2 [label="scaled"] + + left -> scaled_1; + right -> scaled_2; + scaled_1 -> zip; + scaled_2 -> zip; + zip -> Robot; +} diff --git a/docs/images/source_values/robot_pots_2.pdf b/docs/images/source_values/robot_pots_2.pdf new file mode 100644 index 000000000..b82264597 Binary files /dev/null and b/docs/images/source_values/robot_pots_2.pdf differ diff --git a/docs/images/source_values/robot_pots_2.png b/docs/images/source_values/robot_pots_2.png new file mode 100644 index 000000000..a75d4740b Binary files /dev/null and b/docs/images/source_values/robot_pots_2.png differ diff --git a/docs/images/source_values/robot_pots_2.svg b/docs/images/source_values/robot_pots_2.svg new file mode 100644 index 000000000..139e33194 --- /dev/null +++ b/docs/images/source_values/robot_pots_2.svg @@ -0,0 +1,68 @@ + + + + + + +%3 + + +Robot + +Robot + + +left + +Left potentiometer + + +scaled_1 + +scaled + + +left->scaled_1 + + + + +right + +Right potentiometer + + +scaled_2 + +scaled + + +right->scaled_2 + + + + +zip + +zip + + +zip->Robot + + + + +scaled_1->zip + + + + +scaled_2->zip + + + + + diff --git a/docs/images/source_values/sin_values.dot b/docs/images/source_values/sin_values.dot new file mode 100644 index 000000000..2486b2bd5 --- /dev/null +++ b/docs/images/source_values/sin_values.dot @@ -0,0 +1,22 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + Motor + Servo + buzzer [label="Tonal buzzer"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + sin_values + + sin_values -> Motor; + Motor -> Servo; + Motor -> buzzer; +} diff --git a/docs/images/source_values/sin_values.pdf b/docs/images/source_values/sin_values.pdf new file mode 100644 index 000000000..dea2ce44c Binary files /dev/null and b/docs/images/source_values/sin_values.pdf differ diff --git a/docs/images/source_values/sin_values.png b/docs/images/source_values/sin_values.png new file mode 100644 index 000000000..07554d29f Binary files /dev/null and b/docs/images/source_values/sin_values.png differ diff --git a/docs/images/source_values/sin_values.svg b/docs/images/source_values/sin_values.svg new file mode 100644 index 000000000..28f1175ad --- /dev/null +++ b/docs/images/source_values/sin_values.svg @@ -0,0 +1,48 @@ + + + + + + +%3 + + +Motor + +Motor + + +Servo + +Servo + + +Motor->Servo + + + + +buzzer + +Tonal buzzer + + +Motor->buzzer + + + + +sin_values + +sin_values + + +sin_values->Motor + + + + + diff --git a/docs/images/source_values/source_tool.dot b/docs/images/source_values/source_tool.dot new file mode 100644 index 000000000..69ccb9f24 --- /dev/null +++ b/docs/images/source_values/source_tool.dot @@ -0,0 +1,18 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + output [label="Output device"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + tool [label="source tool"] + + tool -> output; +} diff --git a/docs/images/source_values/source_tool.pdf b/docs/images/source_values/source_tool.pdf new file mode 100644 index 000000000..0c8415dec Binary files /dev/null and b/docs/images/source_values/source_tool.pdf differ diff --git a/docs/images/source_values/source_tool.png b/docs/images/source_values/source_tool.png new file mode 100644 index 000000000..ed73794e6 Binary files /dev/null and b/docs/images/source_values/source_tool.png differ diff --git a/docs/images/source_values/source_tool.svg b/docs/images/source_values/source_tool.svg new file mode 100644 index 000000000..b57b4cc5b --- /dev/null +++ b/docs/images/source_values/source_tool.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +output + +Output device + + +tool + +source tool + + +tool->output + + + + + diff --git a/docs/images/source_values/source_tool_candle.dot b/docs/images/source_values/source_tool_candle.dot new file mode 100644 index 000000000..baea0e66a --- /dev/null +++ b/docs/images/source_values/source_tool_candle.dot @@ -0,0 +1,18 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + pwmled [label="PWM LED"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + random_values + + random_values -> pwmled; +} diff --git a/docs/images/source_values/source_tool_candle.pdf b/docs/images/source_values/source_tool_candle.pdf new file mode 100644 index 000000000..413e7179a Binary files /dev/null and b/docs/images/source_values/source_tool_candle.pdf differ diff --git a/docs/images/source_values/source_tool_candle.png b/docs/images/source_values/source_tool_candle.png new file mode 100644 index 000000000..a0e8edbbe Binary files /dev/null and b/docs/images/source_values/source_tool_candle.png differ diff --git a/docs/images/source_values/source_tool_candle.svg b/docs/images/source_values/source_tool_candle.svg new file mode 100644 index 000000000..5a15f19f7 --- /dev/null +++ b/docs/images/source_values/source_tool_candle.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +pwmled + +PWM LED + + +random_values + +random_values + + +random_values->pwmled + + + + + diff --git a/docs/images/source_values/source_tool_value_processor.dot b/docs/images/source_values/source_tool_value_processor.dot new file mode 100644 index 000000000..dcbefdf6f --- /dev/null +++ b/docs/images/source_values/source_tool_value_processor.dot @@ -0,0 +1,19 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + output [label="Output device"] + input [label="Input device"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + tool [label="source tool"] + + input -> tool -> output; +} diff --git a/docs/images/source_values/source_tool_value_processor.pdf b/docs/images/source_values/source_tool_value_processor.pdf new file mode 100644 index 000000000..c2de550e8 Binary files /dev/null and b/docs/images/source_values/source_tool_value_processor.pdf differ diff --git a/docs/images/source_values/source_tool_value_processor.png b/docs/images/source_values/source_tool_value_processor.png new file mode 100644 index 000000000..bb458c833 Binary files /dev/null and b/docs/images/source_values/source_tool_value_processor.png differ diff --git a/docs/images/source_values/source_tool_value_processor.svg b/docs/images/source_values/source_tool_value_processor.svg new file mode 100644 index 000000000..481cac7f2 --- /dev/null +++ b/docs/images/source_values/source_tool_value_processor.svg @@ -0,0 +1,38 @@ + + + + + + +%3 + + +output + +Output device + + +input + +Input device + + +tool + +source tool + + +input->tool + + + + +tool->output + + + + + diff --git a/docs/images/source_values/source_values.dot b/docs/images/source_values/source_values.dot new file mode 100644 index 000000000..dffac2e80 --- /dev/null +++ b/docs/images/source_values/source_values.dot @@ -0,0 +1,12 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + edge [arrowhead=normal, style=solid]; + + input [label="Input device"] + output [label="Output device"] + + input -> output; +} diff --git a/docs/images/source_values/source_values.pdf b/docs/images/source_values/source_values.pdf new file mode 100644 index 000000000..dcb89eef1 Binary files /dev/null and b/docs/images/source_values/source_values.pdf differ diff --git a/docs/images/source_values/source_values.png b/docs/images/source_values/source_values.png new file mode 100644 index 000000000..05881b1ec Binary files /dev/null and b/docs/images/source_values/source_values.png differ diff --git a/docs/images/source_values/source_values.svg b/docs/images/source_values/source_values.svg new file mode 100644 index 000000000..24f61484e --- /dev/null +++ b/docs/images/source_values/source_values.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +input + +Input device + + +output + +Output device + + +input->output + + + + + diff --git a/docs/images/source_values/timed_heat_lamp.dot b/docs/images/source_values/timed_heat_lamp.dot new file mode 100644 index 000000000..1e3257986 --- /dev/null +++ b/docs/images/source_values/timed_heat_lamp.dot @@ -0,0 +1,14 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + lamp [label="Lamp"] + daytime [label="Daytime"] + + daytime -> lamp; +} diff --git a/docs/images/source_values/timed_heat_lamp.pdf b/docs/images/source_values/timed_heat_lamp.pdf new file mode 100644 index 000000000..ed4a53c75 Binary files /dev/null and b/docs/images/source_values/timed_heat_lamp.pdf differ diff --git a/docs/images/source_values/timed_heat_lamp.png b/docs/images/source_values/timed_heat_lamp.png new file mode 100644 index 000000000..c2d871926 Binary files /dev/null and b/docs/images/source_values/timed_heat_lamp.png differ diff --git a/docs/images/source_values/timed_heat_lamp.svg b/docs/images/source_values/timed_heat_lamp.svg new file mode 100644 index 000000000..5828e39dc --- /dev/null +++ b/docs/images/source_values/timed_heat_lamp.svg @@ -0,0 +1,28 @@ + + + + + + +%3 + + +lamp + +Lamp + + +daytime + +Daytime + + +daytime->lamp + + + + + diff --git a/docs/images/source_values/value_processing.dot b/docs/images/source_values/value_processing.dot new file mode 100644 index 000000000..bd7fe055b --- /dev/null +++ b/docs/images/source_values/value_processing.dot @@ -0,0 +1,19 @@ +/* vim: set et sw=4 sts=4: */ + +digraph { + graph [rankdir=RL]; + edge [arrowhead=normal, style=solid]; + + /* Devices */ + node [shape=rect, style=filled, color="#2980b9", fontname=Arial, fontcolor="#ffffff", fontsize=10]; + + output [label="Output device"] + input [label="Input device"] + + /* functions */ + node [shape=oval, style=filled, color="#9ec6e0", fontcolor="#ffffff"]; + + gen [label="custom generator"] + + input -> gen -> output; +} diff --git a/docs/images/source_values/value_processing.pdf b/docs/images/source_values/value_processing.pdf new file mode 100644 index 000000000..e663b239d Binary files /dev/null and b/docs/images/source_values/value_processing.pdf differ diff --git a/docs/images/source_values/value_processing.png b/docs/images/source_values/value_processing.png new file mode 100644 index 000000000..b58f9e11f Binary files /dev/null and b/docs/images/source_values/value_processing.png differ diff --git a/docs/images/source_values/value_processing.svg b/docs/images/source_values/value_processing.svg new file mode 100644 index 000000000..f772f164a --- /dev/null +++ b/docs/images/source_values/value_processing.svg @@ -0,0 +1,38 @@ + + + + + + +%3 + + +output + +Output device + + +input + +Input device + + +gen + +custom generator + + +input->gen + + + + +gen->output + + + + + diff --git a/docs/images/spi_device_hierarchy.dot b/docs/images/spi_device_hierarchy.dot new file mode 100644 index 000000000..475ed97b6 --- /dev/null +++ b/docs/images/spi_device_hierarchy.dot @@ -0,0 +1,46 @@ +digraph classes { + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + + + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + + AnalogInputDevice; + Device; + MCP30xx; + MCP32xx; + MCP33xx; + MCP3xx2; + MCP3xxx; + SPIDevice; + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + + AnalogInputDevice->SPIDevice; + MCP3001->MCP30xx; + MCP3002->MCP30xx; + MCP3002->MCP3xx2; + MCP3004->MCP30xx; + MCP3008->MCP30xx; + MCP30xx->MCP3xxx; + MCP3201->MCP32xx; + MCP3202->MCP32xx; + MCP3202->MCP3xx2; + MCP3204->MCP32xx; + MCP3208->MCP32xx; + MCP32xx->MCP3xxx; + MCP3301->MCP33xx; + MCP3302->MCP33xx; + MCP3304->MCP33xx; + MCP33xx->MCP3xxx; + MCP3xx2->MCP3xxx; + MCP3xxx->AnalogInputDevice; + SPIDevice->Device; +} diff --git a/docs/images/spi_device_hierarchy.pdf b/docs/images/spi_device_hierarchy.pdf new file mode 100644 index 000000000..599afd37a Binary files /dev/null and b/docs/images/spi_device_hierarchy.pdf differ diff --git a/docs/images/spi_device_hierarchy.png b/docs/images/spi_device_hierarchy.png new file mode 100644 index 000000000..f200833c3 Binary files /dev/null and b/docs/images/spi_device_hierarchy.png differ diff --git a/docs/images/spi_device_hierarchy.svg b/docs/images/spi_device_hierarchy.svg new file mode 100644 index 000000000..b4e08ce39 --- /dev/null +++ b/docs/images/spi_device_hierarchy.svg @@ -0,0 +1,247 @@ + + + + + + +classes + + + +AnalogInputDevice + +AnalogInputDevice + + + +SPIDevice + +SPIDevice + + + +AnalogInputDevice->SPIDevice + + + + + +Device + +Device + + + +MCP30xx + +MCP30xx + + + +MCP3xxx + +MCP3xxx + + + +MCP30xx->MCP3xxx + + + + + +MCP32xx + +MCP32xx + + + +MCP32xx->MCP3xxx + + + + + +MCP33xx + +MCP33xx + + + +MCP33xx->MCP3xxx + + + + + +MCP3xx2 + +MCP3xx2 + + + +MCP3xx2->MCP3xxx + + + + + +MCP3xxx->AnalogInputDevice + + + + + +SPIDevice->Device + + + + + +MCP3001 + +MCP3001 + + + +MCP3001->MCP30xx + + + + + +MCP3002 + +MCP3002 + + + +MCP3002->MCP30xx + + + + + +MCP3002->MCP3xx2 + + + + + +MCP3004 + +MCP3004 + + + +MCP3004->MCP30xx + + + + + +MCP3008 + +MCP3008 + + + +MCP3008->MCP30xx + + + + + +MCP3201 + +MCP3201 + + + +MCP3201->MCP32xx + + + + + +MCP3202 + +MCP3202 + + + +MCP3202->MCP32xx + + + + + +MCP3202->MCP3xx2 + + + + + +MCP3204 + +MCP3204 + + + +MCP3204->MCP32xx + + + + + +MCP3208 + +MCP3208 + + + +MCP3208->MCP32xx + + + + + +MCP3301 + +MCP3301 + + + +MCP3301->MCP33xx + + + + + +MCP3302 + +MCP3302 + + + +MCP3302->MCP33xx + + + + + +MCP3304 + +MCP3304 + + + +MCP3304->MCP33xx + + + + + diff --git a/docs/images/traffic_lights.fzz b/docs/images/traffic_lights.fzz new file mode 100644 index 000000000..7c9e5a3fb Binary files /dev/null and b/docs/images/traffic_lights.fzz differ diff --git a/docs/images/traffic_lights_bb.pdf b/docs/images/traffic_lights_bb.pdf new file mode 100644 index 000000000..18518c24f Binary files /dev/null and b/docs/images/traffic_lights_bb.pdf differ diff --git a/docs/images/traffic_lights_bb.png b/docs/images/traffic_lights_bb.png new file mode 100644 index 000000000..d82de87c2 Binary files /dev/null and b/docs/images/traffic_lights_bb.png differ diff --git a/docs/images/traffic_lights_bb.svg b/docs/images/traffic_lights_bb.svg new file mode 100644 index 000000000..68f79c8af --- /dev/null +++ b/docs/images/traffic_lights_bb.svg @@ -0,0 +1,4625 @@ + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1 + + + 5 + + + 5 + + + 10 + + + 10 + + + 15 + + + 15 + + + 20 + + + 20 + + + 25 + + + 25 + + + 30 + + + 30 + + + A + + + A + + + B + + + B + + + C + + + C + + + D + + + D + + + E + + + E + + + F + + + F + + + G + + + G + + + H + + + H + + + I + + + I + + + J + + + J + + + + + + + + + + + + + +Raspberry Pi Model 2 v1.1 + + +© Raspberry Pi 2014 + + + + + + + + + + +h + + +t + + +t + + +p + + +: + + +/ + + +/ + + +w + + +w + + +w + + +. + + +r + + +a + + +s + + +p + + +b + + +e + + +r + + +r + + +y + + +p + + +i + + +. + + +o + + +r + + +g + + + + + + + + + + + + + +Power + + + + + + + + + + + + + + + + +HDMI + + + + + + + + + + + + + + + + +Audio + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +USB 2x + + + + + + + + + + + + + + + + +ETHERNET + + + + + + + + + + + + + + + + +DSI (DISPLAY) + + + + + + + + + + + + + + + + +CSI (CAMERA) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GPIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 8ebf72798..000000000 --- a/docs/index.md +++ /dev/null @@ -1,198 +0,0 @@ -# gpio-zero - -A simple interface to everyday GPIO components used with Raspberry Pi - -## Why? - -The "hello world" program in Java is at least 5 lines long, and contains 11 -jargon words which are to be ignored. The "hello world" program in Python is -one simple line. However, the "hello world" of physical computing in Python -(flashing an LED) is similar to the Java program: - -```python -import RPi.GPIO as GPIO - -GPIO.setmode(GPIO.BCM) -GPIO.setwarnings(False) - -red = 2 - -GPIO.setup(red, GPIO.OUT) - -GPIO.output(red, True) -``` - -6 lines of code to flash an LED. And skipping over why `GPIO` is used twice in -the first line; what `BCM` means; why set warnings to False; and so on. Young -children and beginners shouldn't need to sit and copy out several lines of text -they're told to ignore. They should be able to read their code and understand -what it means. This module provides a simple interface to everyday components. -The LED example becomes: - -```python -from gpiozero import LED - -red = LED(2) - -red.on() -``` - -Any guesses how to turn it off? - -## Implemented Components - -- LED -- Buzzer -- Button -- Motion Sensor -- Light Sensor -- Temperature Sensor -- Motor - -## Usage - -### LED - -Turn an LED on and off repeatedly: - -```python -from gpiozero import LED -from time import sleep - -red = LED(2) - -while True: - red.on() - sleep(1) - red.off() - sleep(1) -``` - -Alternatively: - -```python -from gpiozero import LED -from time import sleep - -red = LED(2) -red.blink(1, 1) -sleep(10) -``` - -### Buzzer - -Turn a buzzer on and off repeatedly: - -```python -from gpiozero import Buzzer -from time import sleep - -buzzer = Buzzer(3) - -while True: - buzzer.on() - sleep(1) - buzzer.off() - sleep(1) -``` - -### Button - -Check if a button is pressed: - -```python -from gpiozero import Button - -button = Button(4) - -if button.is_active: - print("Button is pressed") -else: - print("Button is not pressed") -``` - -Wait for a button to be pressed before continuing: - -```python -from gpiozero import Button - -button = Button(4) - -button.wait_for_input() -print("Button was pressed") -``` - -Run a function every time the button is pressed: - -```python -from gpiozero import Button - -def hello(pin): - print("Button was pressed") - -button = Button(4) - -button.add_callback(hello) -``` - -### Motion Sensor - -Detect motion: - -```python -from gpiozero import MotionSensor - -pir = MotionSensor(5) - -while True: - if pir.motion_detected: - print("Motion detected") -``` - -### Light Sensor - -Retrieve light sensor value: - -```python -from time import sleep -from gpiozero import LightSensor - -sensor = LightSensor(18) -led = LED(16) - -sensor.when_dark = led.on -sensor.when_light = led.off - -while True: - sleep(1) -``` - -### Temperature Sensor - -Retrieve light sensor value: - -```python -from gpiozero import TemperatureSensor - -temperature = TemperatureSensor(6) - -print(temperature.value) -``` - -### Motor - -Drive two motors forwards for 5 seconds: - -```python -from gpiozero import Motor -from time import sleep - -left_motor = Motor(7) -right_motor = Motor(8) - -left_motor.on() -right_motor.on() -sleep(5) -left_motor.off() -right_motor.off() -``` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..a4cdd51ca --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,50 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2021 Dave Jones +.. Copyright (c) 2017-2019 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +.. include:: ../README.rst + +Table of Contents +================= + +.. toctree:: + :maxdepth: 1 + :numbered: + + installing + recipes + recipes_advanced + remote_gpio + recipes_remote_gpio + pi_zero_otg + source_values + cli_tools + faq + compat + migrating_from_rpigpio + contributing + development + api_input + api_output + api_spi + api_boards + api_internal + api_generic + api_tools + api_fonts + api_tones + api_info + api_pins + api_exc + changelog + license + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/installing.rst b/docs/installing.rst new file mode 100644 index 000000000..d4acd603c --- /dev/null +++ b/docs/installing.rst @@ -0,0 +1,117 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2021 Cameron Davidson-Pilon +.. Copyright (c) 2017-2021 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +==================== +Installing GPIO Zero +==================== + +GPIO Zero is installed by default in the `Raspberry Pi OS`_ desktop image, `Raspberry Pi OS`_ Lite image, and +the `Raspberry Pi Desktop`_ image for PC/Mac, all available from +`raspberrypi.com`_. Follow these guides to installing on other operating systems, including for PCs using the :doc:`remote GPIO +` feature. + +.. _Raspberry Pi OS: https://www.raspberrypi.com/software/operating-systems/ +.. _Raspberry Pi Desktop: https://www.raspberrypi.com/software/raspberry-pi-desktop/ +.. _raspberrypi.com: https://www.raspberrypi.com/software/ + +Raspberry Pi +============ + +GPIO Zero is packaged in the apt repositories of Raspberry Pi OS, `Debian`_ and +`Ubuntu`_. It is also available on `PyPI`_. + +.. _Debian: https://packages.debian.org/bookworm/python3-gpiozero +.. _Ubuntu: https://packages.ubuntu.com/noble/python3-gpiozero +.. _PyPI: https://pypi.org/project/gpiozero/ + +apt +--- + +First, update your repositories list: + +.. code-block:: console + + pi@raspberrypi:~$ sudo apt update + +Then install the package for Python 3: + +.. code-block:: console + + pi@raspberrypi:~$ sudo apt install python3-gpiozero + +or Python 2: + +.. code-block:: console + + pi@raspberrypi:~$ sudo apt install python-gpiozero + +pip +--- + +If you're using another operating system on your Raspberry Pi, you may need to +use pip to install GPIO Zero instead. Install pip using `get-pip`_ and then +type: + +.. code-block:: console + + pi@raspberrypi:~$ sudo pip3 install gpiozero + +or for Python 2: + +.. code-block:: console + + pi@raspberrypi:~$ sudo pip install gpiozero + +To install GPIO Zero in a virtual environment, see the :doc:`development` page. + +.. _get-pip: https://pip.pypa.io/en/stable/installing/ + +PC/Mac +====== + +In order to use GPIO Zero's remote GPIO feature from a PC or Mac, you'll need +to install GPIO Zero on that computer using pip. See the :doc:`remote_gpio` +page for more information. + +Documentation +============= + +This documentation is also available for offline installation like so: + +.. code-block:: console + + pi@raspberrypi:~$ sudo apt install python-gpiozero-doc + +This will install the HTML version of the documentation under the +:file:`/usr/share/doc/python-gpiozero-doc/html` path. To view the offline +documentation you have several options: + +You can open the documentation directly by visiting +file:///usr/share/doc/python-gpiozero-doc/html/index.html in your browser. +However, be aware that using ``file://`` URLs sometimes breaks certain elements. +To avoid this, you can view the docs from an ``http://`` style URL by starting +a trivial HTTP server with Python, like so: + +.. code-block:: console + + $ python3 -m http.server -d /usr/share/doc/python-gpiozero-doc/html + +Then visit http://localhost:8000/ in your browser. + +Alternatively, the package also integrates into Debian's `doc-base`_ system, so +you can install one of the doc-base clients (dochelp, dwww, dhelp, doc-central, +etc.) and use its interface to locate this document. + +If you want to view the documentation offline on a different device, such as an +eReader, there are Epub and PDF versions of the documentation available for +download from the `ReadTheDocs site`_. Simply click on the "Read the Docs" box +at the bottom-left corner of the page (under the table of contents) and select +"PDF" or "Epub" from the "Downloads" section. + +.. _doc-base: https://wiki.debian.org/doc-base +.. _ReadTheDocs site: https://gpiozero.readthedocs.io/ diff --git a/docs/license.rst b/docs/license.rst new file mode 100644 index 000000000..57da34af4 --- /dev/null +++ b/docs/license.rst @@ -0,0 +1,10 @@ +.. _license: + +======= +License +======= + +Copyright © 2015-2020 Ben Nuttall and contributors; see +:doc:`index` for current list + +.. include:: ../LICENSE.rst diff --git a/docs/migrating_from_rpigpio.rst b/docs/migrating_from_rpigpio.rst new file mode 100644 index 000000000..7a575aa46 --- /dev/null +++ b/docs/migrating_from_rpigpio.rst @@ -0,0 +1,387 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2019-2023 Dave Jones +.. Copyright (c) 2021 Rimas Misevičius +.. Copyright (c) 2019-2021 Ben Nuttall +.. Copyright (c) 2020 damosurfer <35042619+damosurfer@users.noreply.github.com> +.. Copyright (c) 2020 Andrew Scheller +.. Copyright (c) 2019 Steveis +.. +.. SPDX-License-Identifier: BSD-3-Clause + +.. _migrating_from_rpigpio: + +======================= +Migrating from RPi.GPIO +======================= + +.. currentmodule:: gpiozero + +If you are familiar with the `RPi.GPIO`_ library, you will be used to writing +code which deals with *pins* and the *state of pins*. You will see from the +examples in this documentation that we generally refer to things like LEDs and +Buttons rather than input pins and output pins. + +GPIO Zero provides classes which represent *devices*, so instead of having a +pin number and telling it to go high, you have an LED and you tell it to turn +on, and instead of having a pin number and asking if it's high or low, you have +a button and ask if it's pressed. There is also no boilerplate code to get +started — you just import the parts you need. + +GPIO Zero provides many device classes, each with specific methods and +properties bespoke to that device. For example, the functionality for an +HC-SR04 Distance Sensor can be found in the :class:`DistanceSensor` class. + +As well as specific device classes, we provide base classes +:class:`InputDevice` and :class:`OutputDevice`. One main difference between +these and the equivalents in RPi.GPIO is that they are classes, not functions, +which means that you initialize one to begin, and provide its pin number, but +then you never need to use the pin number again, as it's stored by the object. + +GPIO Zero was originally just a layer on top of RPi.GPIO, but we later added +support for various other underlying pin libraries. RPi.GPIO is currently the +default pin library used. Read more about this in :ref:`changing-pin-factory`. + + +Output devices +============== + +Turning an LED on in `RPi.GPIO`_:: + + import RPi.GPIO as GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + GPIO.setup(2, GPIO.OUT) + + GPIO.output(2, GPIO.HIGH) + +Turning an LED on in GPIO Zero:: + + from gpiozero import LED + + led = LED(2) + + led.on() + +The :class:`LED` class also supports threaded blinking through the +:meth:`~LED.blink` method. + +:class:`OutputDevice` is the base class for output devices, and can be used in a +similar way to output devices in RPi.GPIO. + +See a full list of supported :doc:`output devices `. Other output +devices have similar property and method names. There is commonality in naming +at base level, such as :attr:`OutputDevice.is_active`, which is aliased in a +device class, such as :attr:`LED.is_lit`. + + +Input devices +============= + +Reading a button press in `RPi.GPIO`_:: + + import RPi.GPIO as GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) + + if not GPIO.input(4): + print("button is pressed") + +Reading a button press in GPIO Zero:: + + from gpiozero import Button + + btn = Button(4) + + if btn.is_pressed: + print("button is pressed") + +Note that in the RPi.GPIO example, the button is set up with the option +``GPIO.PUD_UP`` which means "pull-up", and therefore when the button is not +pressed, the pin is high. When the button is pressed, the pin goes low, so the +condition requires negation (``if not``). If the button was configured as +pull-down, the logic is reversed and the condition would become ``if +GPIO.input(4)``:: + + import RPi.GPIO as GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + GPIO.setup(4, GPIO.IN, GPIO.PUD_DOWN) + + if GPIO.input(4): + print("button is pressed") + +In GPIO Zero, the default configuration for a button is pull-up, but this can +be configured at initialization, and the rest of the code stays the same:: + + from gpiozero import Button + + btn = Button(4, pull_up=False) + + if btn.is_pressed: + print("button is pressed") + +RPi.GPIO also supports blocking edge detection. + +Wait for a pull-up button to be pressed in RPi.GPIO:: + + import RPi.GPIO as GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) + + GPIO.wait_for_edge(4, GPIO.FALLING): + print("button was pressed") + +The equivalent in GPIO Zero:: + + from gpiozero import Button + + btn = Button(4) + + btn.wait_for_press() + print("button was pressed") + +Again, if the button is pulled down, the logic is reversed. Instead of waiting +for a falling edge, we're waiting for a rising edge:: + + import RPi.GPIO as GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) + + GPIO.wait_for_edge(4, GPIO.FALLING): + print("button was pressed") + +Again, in GPIO Zero, the only difference is in the initialization:: + + from gpiozero import Button + + btn = Button(4, pull_up=False) + + btn.wait_for_press() + print("button was pressed") + +RPi.GPIO has threaded callbacks. You create a function (which must take one +argument), and pass it in to ``add_event_detect``, along with the pin number +and the edge direction:: + + import RPi.GPIO as GPIO + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + def pressed(pin): + print("button was pressed") + + def released(pin): + print("button was released") + + GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) + + GPIO.add_event_detect(4, GPIO.FALLING, pressed) + GPIO.add_event_detect(4, GPIO.RISING, released) + +In GPIO Zero, you assign the :attr:`~Button.when_pressed` and +:attr:`~Button.when_released` properties to set up callbacks on those actions:: + + from gpiozero import Button + + def pressed(): + print("button was pressed") + + def released(): + print("button was released") + + btn = Button(4) + + btn.when_pressed = pressed + btn.when_released = released + +:attr:`~Button.when_held` is also provided, where the length of time considered +a "hold" is configurable. + +The callback functions don't have to take any arguments, but if they take one, +the button object is passed in, allowing you to determine which button called +the function. + +:class:`InputDevice` is the base class for input devices, and can be used in a +similar way to input devices in RPi.GPIO. + +See a full list of :doc:`input devices `. Other input devices have +similar property and method names. There is commonality in naming at base level, +such as :attr:`InputDevice.is_active`, which is aliased in a device class, such +as :attr:`Button.is_pressed` and :attr:`LightSensor.light_detected`. + + +Composite devices, boards and accessories +========================================= + +Some devices require connections to multiple pins, for example a distance +sensor, a combination of LEDs or a HAT. Some GPIO Zero devices comprise +multiple device connections within one object, such as :class:`RGBLED`, +:class:`LEDBoard`, :class:`DistanceSensor`, :class:`Motor` and :class:`Robot`. + +With RPi.GPIO, you would have one output pin for the trigger, and one input pin +for the echo. You would time the echo and calculate the distance. With GPIO +Zero, you create a single :class:`DistanceSensor` object, specifying the +trigger and echo pins, and you would read the :attr:`DistanceSensor.distance` +property which automatically calculates the distance within the implementation +of the class. + +The :class:`Motor` class controls two output pins to drive the motor forwards +or backwards. The :class:`Robot` class controls four output pins (two motors) +in the right combination to drive a robot forwards or backwards, and turn left +and right. + +The :class:`LEDBoard` class takes an arbitrary number of pins, each controlling +a single LED. The resulting :class:`LEDBoard` object can be used to control all +LEDs together (all on / all off), or individually by index. Also the object can +be iterated over to turn LEDs on in order. See examples of this (including +slicing) in the :ref:`advanced recipes `. + + +PWM (Pulse-width modulation) +============================ + +Both libraries support software PWM control on any pin. Depending on the pin +library used, GPIO Zero can also support hardware PWM (using +:class:`~pins.rpigpio.RPIOPin` or :class:`~pins.rpigpio.PiGPIOPin`). + +A simple example of using PWM is to control the brightness of an LED. + +In `RPi.GPIO`_:: + + import RPi.GPIO as GPIO + from time import sleep + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + + GPIO.setup(2, GPIO.OUT) + pwm = GPIO.PWM(2, 100) + pwm.start(0) + + for dc in range(101): + pwm.changeDutyCycle(dc) + sleep(0.01) + +In GPIO Zero:: + + from gpiozero import PWMLED + from time import sleep + + led = PWMLED(2) + + for b in range(101): + led.value = b / 100.0 + sleep(0.01) + +:class:`PWMLED` has a :meth:`~PWMLED.blink` method which can be used the same +was as :class:`LED`'s :meth:`~LED.blink` method, but its PWM capabilities allow +for ``fade_in`` and ``fade_out`` options to be provided. There is also the +:meth:`~PWMLED.pulse` method which provides a neat way to have an LED fade in +and out repeatedly. + +Other devices can make use of PWM, such as motors (for variable speed) and +servos. See the :class:`Motor`, :class:`Servo` and :class:`AngularServo` +classes for information on those. :class:`Motor` and :class:`Robot` default to +using PWM, but it can be disabled with ``pwm=False`` at initialization. Servos +cannot be used without PWM. Devices containing LEDs default to not using PWM, +but ``pwm=True`` can be specified and any LED objects within the device will be +initialized as :class:`PWMLED` objects. + + +Cleanup +======= + +Pin state cleanup is explicit in RPi.GPIO, and is done manually with +``GPIO.cleanup()`` but in GPIO Zero, cleanup is automatically performed on every +pin used, at the end of the script. Manual cleanup is possible by use of the +:meth:`~Device.close` method on the device. + +Note that cleanup only occurs at the point of normal termination of the script. +If the script exits due to a program error, cleanup will not be performed. To +ensure that cleanup is performed after an exception is raised, the exception +must be handled, for example:: + + from gpiozero import Button + + btn = Button(4) + + while True: + try: + if btn.is_pressed: + print("Pressed") + except KeyboardInterrupt: + print("Ending program") + +Read more in the relevant FAQ: :ref:`gpio-cleanup` + + +Pi Information +============== + +RPi.GPIO provides information about the Pi you're using. The equivalent in GPIO +Zero is the function :func:`pi_info`: + +.. code-block:: pycon + + >>> from gpiozero import pi_info + >>> pi = pi_info() + >>> pi + PiBoardInfo(revision='a02082', model='3B', pcb_revision='1.2', released='2016Q1', soc='BCM2837', manufacturer='Sony', memory=1024, storage='MicroSD', usb=4, ethernet=1, wifi=True, bluetooth=True, csi=1, dsi=1, headers=..., board=...) + >>> pi.soc + 'BCM2837' + >>> pi.wifi + True + +Read more about what :class:`PiBoardInfo` provides. + + +More +==== + +GPIO Zero provides more than just GPIO device support, it includes some support +for :doc:`SPI devices ` including a range of analog to digital +converters. + +Device classes which are compatible with other GPIO devices, but have no +relation to GPIO pins, such as :class:`CPUTemperature`, :class:`TimeOfDay`, +:class:`PingServer` and :class:`LoadAverage` are also provided. + +GPIO Zero features support for multiple pin libraries. The default is to use +``RPi.GPIO`` to control the pins, but you can choose to use another library, +such as ``pigpio``, which supports network controlled GPIO. See +:ref:`changing-pin-factory` and :doc:`remote_gpio` for more information. + +It is possible to run GPIO Zero on your PC, both for remote GPIO and for testing +purposes, using :ref:`mock-pins`. + +Another feature of this library is configuring devices to be connected together +in a logical way, for example in one line you can say that an LED and button are +"paired", i.e. the button being pressed turns the LED on. Read about this in +:doc:`source_values`. + + +FAQs +==== + +Note the following FAQs which may catch out users too familiar with RPi.GPIO: + +* :ref:`keep-your-script-running` +* :ref:`pinfactoryfallback-warnings` +* :ref:`gpio-cleanup` + +.. _RPi.GPIO: https://pypi.org/project/RPi.GPIO/ diff --git a/docs/pi_zero_otg.rst b/docs/pi_zero_otg.rst new file mode 100644 index 000000000..fc6ba1e8f --- /dev/null +++ b/docs/pi_zero_otg.rst @@ -0,0 +1,153 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2019-2023 Dave Jones +.. Copyright (c) 2018-2021 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +=============== +Pi Zero USB OTG +=============== + +The `Raspberry Pi Zero`_ and `Pi Zero W`_ feature a USB OTG port, allowing +users to configure the device as (amongst other things) an Ethernet device. In +this mode, it is possible to control the Pi Zero's GPIO pins over USB from +another computer using the :doc:`remote GPIO ` feature. + + +GPIO expander method - no SD card required +========================================== + +The GPIO expander method allows you to boot the Pi Zero over USB from the PC, +without an SD card. Your PC sends the required boot firmware to the Pi over the +USB cable, launching a mini version of Raspberry Pi OS and booting it in RAM. +The OS then starts the pigpio daemon, allowing "remote" access over the USB +cable. + +At the time of writing, this is only possible using either the Raspberry Pi +Desktop x86 OS, or Ubuntu (or a derivative), or from another Raspberry Pi. +Usage from Windows and Mac OS is not supported at present. + +Raspberry Pi Desktop x86 setup +------------------------------ + +1. Download an ISO of the `Raspberry Pi Desktop OS`_ from raspberrypi.com + +2. Write the image to a USB stick or burn to a DVD. + +3. Live boot your PC or Mac into the OS (select "Run with persistence" and your + computer will be back to normal afterwards). + +Raspberry Pi setup (using Raspberry Pi OS) +------------------------------------------ + +1. Update your package list and install the ``usbbootgui`` package: + +.. code-block:: console + + $ sudo apt update + $ sudo apt install usbbootgui + +Ubuntu setup +------------ + +1. Add the Raspberry Pi PPA to your system: + +.. code-block:: console + + $ sudo add-apt-repository ppa:rpi-distro/ppa + +2. If you have previously installed ``gpiozero`` or ``pigpio`` with pip, +uninstall these first: + +.. code-block:: console + + $ sudo pip3 uninstall gpiozero pigpio + +3. Install the required packages from the PPA: + +.. code-block:: console + + $ sudo apt install usbbootgui pigpio python3-gpiozero python3-pigpio + +Access the GPIOs +---------------- + +Once your PC or Pi has the USB Boot GUI tool installed, connecting a Pi Zero +will automatically launch a prompt to select a role for the device. Select +"GPIO expansion board" and continue: + +.. image:: images/gpio-expansion-prompt.png + :align: center + :width: 364px + +It will take 30 seconds or so to flash it, then the dialogue will disappear. + +Raspberry Pi OS will name your Pi Zero connection ``usb0``. On Ubuntu, this will +likely be something else. You can ping it using the address ``fe80::1%`` +followed by the connection string. You can look this up using ``ifconfig``. + +Set the :envvar:`GPIOZERO_PIN_FACTORY` and :envvar:`PIGPIO_ADDR` environment +variables on your PC so GPIO Zero connects to the "remote" Pi Zero: + +.. code-block:: console + + $ export GPIOZERO_PIN_FACTORY=pigpio + $ export PIGPIO_ADDR=fe80::1%usb0 + +Now any GPIO Zero code you run on the PC will use the GPIOs of the attached Pi +Zero: + +.. image:: images/gpio-expansion-example.png + :align: center + :width: 640px + +Alternatively, you can set the pin factory in-line, as explained in +:doc:`remote_gpio`. + +Read more on the GPIO expander in blog posts on `raspberrypi.com`_ and +`bennuttall.com`_. + +Legacy method - SD card required +================================ + +The legacy method requires the Pi Zero to have an SD card with Raspberry Pi OS +inserted. + +Start by creating a Raspberry Pi OS (desktop or lite) SD card, and then +configure the boot partition like so: + +1. Edit :file:`config.txt` and add ``dtoverlay=dwc2`` on a new line, then save + the file. + +2. Create an empty file called :file:`ssh` (no file extension) and save it in + the boot partition. + +3. Edit :file:`cmdline.txt`` and insert ``modules-load=dwc2,g_ether`` after + ``rootwait``. + +(See guides on `blog.gbaman.info`_ and `learn.adafruit.com`_ for more detailed +instructions) + +Then connect the Pi Zero to your computer using a micro USB cable (connecting +it to the USB port, not the power port). You'll see the indicator LED flashing +as the Pi Zero boots. When it's ready, you will be able to ping and SSH into it +using the hostname ``raspberrypi.local``. SSH into the Pi Zero, install pigpio +and run the pigpio daemon. + +Then, drop out of the SSH session and you can run Python code on your computer +to control devices attached to the Pi Zero, referencing it by its hostname (or +IP address if you know it), for example: + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=raspberrypi.local python3 led.py + + +.. _Raspberry Pi Zero: https://www.raspberrypi.com/products/raspberry-pi-zero/ +.. _Pi Zero W: https://www.raspberrypi.com/products/raspberry-pi-zero-w/ +.. _Raspberry Pi Desktop OS: https://www.raspberrypi.com/software/raspberry-pi-desktop/ +.. _raspberrypi.com: https://www.raspberrypi.com/news/gpio-expander/ +.. _bennuttall.com: http://bennuttall.com/raspberry-pi-zero-gpio-expander/ +.. _blog.gbaman.info: http://blog.gbaman.info/?p=791 +.. _learn.adafruit.com: https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget diff --git a/docs/recipes.rst b/docs/recipes.rst new file mode 100644 index 000000000..701ef9797 --- /dev/null +++ b/docs/recipes.rst @@ -0,0 +1,572 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2015-2023 Dave Jones +.. Copyright (c) 2016-2019 Ben Nuttall +.. Copyright (c) 2016 Barry Byford +.. Copyright (c) 2016 Andrew Scheller +.. +.. SPDX-License-Identifier: BSD-3-Clause + +============= +Basic Recipes +============= + +.. currentmodule:: gpiozero + +The following recipes demonstrate some of the capabilities of the GPIO Zero +library. Please note that all recipes are written assuming Python 3. Recipes +*may* work under Python 2, but no guarantees! + +Importing GPIO Zero +=================== + +.. module:: gpiozero + +In Python, libraries and functions used in a script must be imported by name +at the top of the file, with the exception of the functions built into Python +by default. + +For example, to use the :class:`Button` interface from GPIO Zero, it +should be explicitly imported:: + + from gpiozero import Button + +Now :class:`~gpiozero.Button` is available directly in your script:: + + button = Button(2) + +Alternatively, the whole GPIO Zero library can be imported:: + + import gpiozero + +In this case, all references to items within GPIO Zero must be prefixed:: + + button = gpiozero.Button(2) + +.. _pin-numbering: + +Pin Numbering +============= + +This library uses Broadcom (BCM) pin numbering for the GPIO pins, as opposed to +physical (BOARD) numbering. Unlike in the `RPi.GPIO`_ library, this is not +configurable. However, translation from other schemes can be used by providing +prefixes to pin numbers (see below). + +Any pin marked "GPIO" in the diagram below can be used as a pin number. For +example, if an LED was attached to "GPIO17" you would specify the pin number as +17 rather than 11: + +.. image:: images/pin_layout.* + :align: center + +If you wish to use physical (BOARD) numbering you can specify the pin number as +"BOARD11". If you are familiar with the `wiringPi`_ pin numbers (another +physical layout) you could use "WPI0" instead. Finally, you can specify pins as +"header:number", e.g. "J8:11" meaning physical pin 11 on header J8 (the GPIO +header on modern Pis). Hence, the following lines are all equivalent: + +.. code-block:: pycon + + >>> led = LED(17) + >>> led = LED("GPIO17") + >>> led = LED("BCM17") + >>> led = LED("BOARD11") + >>> led = LED("WPI0") + >>> led = LED("J8:11") + +Note that these alternate schemes are merely translations. If you request the +state of a device on the command line, the associated pin number will *always* +be reported in the Broadcom (BCM) scheme: + +.. code-block:: pycon + + >>> led = LED("BOARD11") + >>> led + + +Throughout this manual we will use the default integer pin numbers, in the +Broadcom (BCM) layout shown above. + +.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO +.. _wiringPi: https://projects.drogon.net/raspberry-pi/wiringpi/pins/ + +LED +=== + +.. image:: images/led_bb.* + +Turn an :class:`LED` on and off repeatedly: + +.. literalinclude:: examples/led_1.py + +Alternatively: + +.. literalinclude:: examples/led_2.py + +.. note:: + + Reaching the end of a Python script will terminate the process and GPIOs + may be reset. Keep your script alive with :func:`signal.pause`. See + :ref:`keep-your-script-running` for more information. + +LED with variable brightness +============================ + +.. image:: images/led_bb.* + +Any regular LED can have its brightness value set using PWM +(pulse-width-modulation). In GPIO Zero, this can be achieved using +:class:`PWMLED` using values between 0 and 1: + +.. literalinclude:: examples/led_variable_brightness.py + +Similarly to blinking on and off continuously, a PWMLED can pulse (fade in and +out continuously): + +.. literalinclude:: examples/led_pulse.py + +Button +====== + +.. image:: images/button_bb.* + +Check if a :class:`Button` is pressed: + +.. literalinclude:: examples/button_1.py + +Wait for a button to be pressed before continuing: + +.. literalinclude:: examples/button_2.py + +Run a function every time the button is pressed: + +.. literalinclude:: examples/button_3.py + :emphasize-lines: 9 + +.. note:: + + Note that the line ``button.when_pressed = say_hello`` does not run the + function ``say_hello``, rather it creates a reference to the function to be + called when the button is pressed. Accidental use of ``button.when_pressed + = say_hello()`` would set the ``when_pressed`` action to :data:`None` (the + return value of this function) which would mean nothing happens when the + button is pressed. + +Similarly, functions can be attached to button releases: + +.. literalinclude:: examples/button_4.py + +Button controlled LED +===================== + +.. image:: images/led_button_bb.* + +Turn on an :class:`LED` when a :class:`Button` is pressed: + +.. literalinclude:: examples/button_led_1.py + +Alternatively: + +.. literalinclude:: examples/button_led_2.py + +Button controlled camera +======================== + +Using the button press to trigger :class:`~picamera.PiCamera` to take a picture +using ``button.when_pressed = camera.capture`` would not work because the +:meth:`~picamera.PiCamera.capture` method requires an ``output`` parameter. +However, this can be achieved using a custom function which requires no +parameters: + +.. literalinclude:: examples/button_camera_1.py + :emphasize-lines: 9-11 + +Another example could use one button to start and stop the camera preview, and +another to capture: + +.. literalinclude:: examples/button_camera_2.py + +Shutdown button +=============== + +The :class:`Button` class also provides the ability to run a function when the +button has been held for a given length of time. This example will shut down +the Raspberry Pi when the button is held for 2 seconds: + +.. literalinclude:: examples/button_shutdown.py + +LEDBoard +======== + +.. image:: images/ledboard_bb.* + +A collection of LEDs can be accessed using :class:`LEDBoard`: + +.. literalinclude:: examples/led_board_1.py + +Using :class:`LEDBoard` with ``pwm=True`` allows each LED's brightness to be +controlled: + +.. literalinclude:: examples/led_board_2.py + +See more :class:`LEDBoard` examples in the :ref:`advanced LEDBoard recipes +`. + +LEDBarGraph +=========== + +.. image:: images/ledbargraph_bb.* + +A collection of LEDs can be treated like a bar graph using +:class:`LEDBarGraph`: + +.. literalinclude:: examples/led_bargraph_1.py + +Note values are essentially rounded to account for the fact LEDs can only be on +or off when ``pwm=False`` (the default). + +However, using :class:`LEDBarGraph` with ``pwm=True`` allows more precise +values using LED brightness: + +.. literalinclude:: examples/led_bargraph_2.py + +LEDCharDisplay +============== + +.. image:: images/led_char_display_bb.* + +A common `7-segment display`_ can be used to represent a variety of characters +using :class:`LEDCharDisplay` (which actually supports an arbitrary number of +segments): + +.. literalinclude:: examples/led_char_display.py + +Alternatively: + +.. literalinclude:: examples/led_char_source.py + +See a multi-character example in the :ref:`advanced recipes +` chapter. + +Traffic Lights +============== + +.. image:: images/traffic_lights_bb.* + +A full traffic lights system. + +Using a :class:`TrafficLights` kit like Pi-Stop: + +.. literalinclude:: examples/traffic_lights_1.py + +Alternatively: + +.. literalinclude:: examples/traffic_lights_2.py + +Using :class:`LED` components: + +.. literalinclude:: examples/traffic_lights_3.py + +Push button stop motion +======================= + +Capture a picture with the camera module every time a button is pressed: + +.. literalinclude:: examples/button_stop_motion.py + +See `Push Button Stop Motion`_ for a full resource. + +Reaction Game +============= + +.. image:: images/reaction_game_bb.* + +When you see the light come on, the first person to press their button wins! + +.. literalinclude:: examples/reaction_game.py + +See `Quick Reaction Game`_ for a full resource. + +GPIO Music Box +============== + +.. image:: images/music_box_bb.* + +Each button plays a different sound! + +.. literalinclude:: examples/music_box.py + +See `GPIO Music Box`_ for a full resource. + +All on when pressed +=================== + +While the button is pressed down, the buzzer and all the lights come on. + +:class:`FishDish`: + +.. literalinclude:: examples/all_on_1.py + +Ryanteck :class:`TrafficHat`: + +.. literalinclude:: examples/all_on_2.py + +Using :class:`LED`, :class:`Buzzer`, and :class:`Button` components: + +.. literalinclude:: examples/all_on_3.py + +Full color LED +============== + +.. image:: images/rgb_led_bb.* + +Making colours with an :class:`RGBLED`: + +.. literalinclude:: examples/rgbled.py + +Motion sensor +============= + +.. image:: images/motion_sensor_bb.* + +Light an :class:`LED` when a :class:`MotionSensor` detects motion: + +.. literalinclude:: examples/motion_sensor.py + +Light sensor +============ + +.. image:: images/light_sensor_bb.* + +Have a :class:`LightSensor` detect light and dark: + +.. literalinclude:: examples/light_sensor_1.py + +Run a function when the light changes: + +.. literalinclude:: examples/light_sensor_2.py + +Or make a :class:`PWMLED` change brightness according to the detected light +level: + +.. literalinclude:: examples/light_sensor_3.py + +Distance sensor +=============== + +.. image:: images/distance_sensor_bb.* + +.. note:: + + In the diagram above, the wires leading from the sensor to the breadboard + can be omitted; simply plug the sensor directly into the breadboard facing + the edge (unfortunately this is difficult to illustrate in the diagram + without the sensor's diagram obscuring most of the breadboard!) + +Have a :class:`DistanceSensor` detect the distance to the nearest object: + +.. literalinclude:: examples/distance_sensor_1.py + +Run a function when something gets near the sensor: + +.. literalinclude:: examples/distance_sensor_2.py + +Rotary encoder +============== + +.. image:: images/color_picker_bb.* + +.. note:: + + In this recipe, I've used a common *anode* RGB LED. Often, Pi projects use + common *cathode* RGB LEDs because they're slightly easier to think about + electrically. However, in this case all three components can be found in an + illuminated rotary encoder which incorporates a common anode RGB LED, and a + momentary push button. This is also the reason for the button being wired + active-low, contrary to most other examples in this documentation. + + For the sake of clarity, the diagram shows the three separate components, + but this same circuit will work equally well with this commonly available + `illuminated rotary encoder`_ instead. + +Have a :class:`RotaryEncoder`, an :class:`RGBLED`, and :class:`Button` act as a +color picker: + +.. literalinclude:: examples/color_picker.py + +Servo +===== + +Control a :class:`Servo` between its minimum, mid-point and maximum positions +in sequence: + +.. literalinclude:: examples/servo_1.py + +Use a button to control the :class:`Servo` between its minimum and maximum +positions: + +.. literalinclude:: examples/servo_2.py + +Automate the :class:`Servo` to continuously slowly sweep: + +.. literalinclude:: examples/servo_sweep.py + +Use :class:`AngularServo` so you can specify an angle: + +.. literalinclude:: examples/angular_servo.py + +Motors +====== + +.. image:: images/motor_bb.* + +Spin a :class:`Motor` around forwards and backwards: + +.. literalinclude:: examples/motor.py + +Robot +===== + +.. image:: images/robot_bb.* + +Make a :class:`Robot` drive around in (roughly) a square: + +.. literalinclude:: examples/robot_1.py + +Make a :class:`Robot` with a :class:`DistanceSensor` that runs away when things +get within 20cm of it: + +.. literalinclude:: examples/robot_2.py + +Button controlled robot +======================= + +.. image:: images/button_robot_bb.* + +Use four GPIO buttons as forward/back/left/right controls for a :class:`Robot`: + +.. literalinclude:: examples/robot_buttons_1.py + +Keyboard controlled robot +========================= + +.. image:: images/robot_bb.* + +Use up/down/left/right keys to control a :class:`Robot`: + +.. literalinclude:: examples/robot_keyboard_1.py + +.. note:: + + This recipe uses the standard :mod:`curses` module. This module requires + that Python is running in a terminal in order to work correctly, hence this + recipe will *not* work in environments like IDLE. + +If you prefer a version that works under IDLE, the following recipe should +suffice: + +.. literalinclude:: examples/robot_keyboard_2.py + +.. note:: + + This recipe uses the third-party ``evdev`` module. Install this library + with ``sudo pip3 install evdev`` first. Be aware that ``evdev`` will only + work with local input devices; this recipe will *not* work over SSH. + +Motion sensor robot +=================== + +.. image:: images/motion_robot_bb.* + +Make a robot drive forward when it detects motion: + +.. literalinclude:: examples/robot_motion_1.py + +Alternatively: + +.. literalinclude:: examples/robot_motion_2.py + +Potentiometer +============= + +.. image:: images/potentiometer_bb.* + +Continually print the value of a potentiometer (values between 0 and 1) +connected to a :class:`MCP3008` analog to digital converter: + +.. literalinclude:: examples/pot_1.py + +Present the value of a potentiometer on an LED bar graph using PWM to represent +states that won't "fill" an LED: + +.. literalinclude:: examples/pot_2.py + +Measure temperature with an ADC +=============================== + +.. IMAGE TBD + +Wire a TMP36 temperature sensor to the first channel of an :class:`MCP3008` +analog to digital converter: + +.. literalinclude:: examples/thermometer.py + +Full color LED controlled by 3 potentiometers +============================================= + +.. image:: images/rgbled_pot_bb.* + +Wire up three potentiometers (for red, green and blue) and use each of their +values to make up the colour of the LED: + +.. literalinclude:: examples/rgbled_pot_1.py + +Alternatively, the following example is identical, but uses the +:attr:`~SourceMixin.source` property rather than a :keyword:`while` loop: + +.. literalinclude:: examples/rgbled_pot_2.py + +Timed heat lamp +=============== + +If you have a pet (e.g. a tortoise) which requires a heat lamp to be switched +on for a certain amount of time each day, you can use an `Energenie Pi-mote`_ +to remotely control the lamp, and the :class:`TimeOfDay` class to control the +timing: + +.. literalinclude:: examples/timed_heat_lamp.py + +Internet connection status indicator +==================================== + +You can use a pair of green and red LEDs to indicate whether or not your +internet connection is working. Simply use the :class:`PingServer` class to +identify whether a ping to `google.com` is successful. If successful, the green +LED is lit, and if not, the red LED is lit: + +.. literalinclude:: examples/internet_status_indicator.py + +CPU Temperature Bar Graph +========================= + +You can read the Raspberry Pi's own CPU temperature using the built-in +:class:`CPUTemperature` class, and display this on a "bar graph" of LEDs: + +.. literalinclude:: examples/cpu_temperature_bar_graph.py + +More recipes +============ + +Continue to: + +* :doc:`recipes_advanced` +* :doc:`recipes_remote_gpio` + + +.. _Push Button Stop Motion: https://projects.raspberrypi.org/en/projects/push-button-stop-motion +.. _Quick Reaction Game: https://projects.raspberrypi.org/en/projects/python-quick-reaction-game +.. _GPIO Music Box: https://projects.raspberrypi.org/en/projects/gpio-music-box +.. _Energenie Pi-mote: https://energenie4u.co.uk/catalogue/product/ENER002-2PI +.. _illuminated rotary encoder: https://shop.pimoroni.com/products/rotary-encoder-illuminated-rgb +.. _7-segment display: https://en.wikipedia.org/wiki/Seven-segment_display diff --git a/docs/recipes_advanced.rst b/docs/recipes_advanced.rst new file mode 100644 index 000000000..0cdc3b50c --- /dev/null +++ b/docs/recipes_advanced.rst @@ -0,0 +1,207 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2021 Dave Jones +.. Copyright (c) 2017-2019 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +================ +Advanced Recipes +================ + +.. currentmodule:: gpiozero + +The following recipes demonstrate some of the capabilities of the GPIO Zero +library. Please note that all recipes are written assuming Python 3. Recipes +*may* work under Python 2, but no guarantees! + +.. _ledboard-advanced: + +LEDBoard +======== + +You can iterate over the LEDs in a :class:`LEDBoard` object one-by-one: + +.. literalinclude:: examples/led_board_3.py + +:class:`LEDBoard` also supports indexing. This means you can access the +individual :class:`LED` objects using ``leds[i]`` where ``i`` is an integer +from 0 up to (not including) the number of LEDs: + +.. literalinclude:: examples/led_board_4.py + +This also means you can use slicing to access a subset of the LEDs: + +.. literalinclude:: examples/led_board_5.py + +:class:`LEDBoard` objects can have their `LED` objects named upon construction. +This means the individual LEDs can be accessed by their name: + +.. literalinclude:: examples/led_board_6.py + +:class:`LEDBoard` objects can also be nested within other :class:`LEDBoard` +objects: + +.. literalinclude:: examples/led_board_7.py + +.. _multichar-display: + +Multi-character 7-segment display +================================= + +The 7-segment display demonstrated in the previous chapter is often available +in multi-character variants (typically 4 characters long). Such displays are +multiplexed meaning that the LED pins are typically the same as for the single +character display but are shared across all characters. Each character in turn +then has its own common line which can be tied to ground (in the case of a +common cathode display) to enable that particular character. By activating each +character in turn very quickly, the eye can be fooled into thinking four +different characters are being displayed simultaneously. + +In such circuits you should not attempt to sink all the current from a single +character (which may have up to 8 LEDs, in the case of a decimal-point, active) +into a single GPIO. Rather, use some appropriate transistor (or similar +component, e.g. an opto-coupler) to tie the digit's cathode to ground, and +control that component from a GPIO. + +.. image:: images/7seg_multi_bb.* + +This circuit demonstrates a 4-character 7-segment (actually 8-segment, with +decimal-point) display, controlled by the Pi's GPIOs with 4 2N-3904 NPN +transistors to control the digits. + +.. warning:: + + You are strongly advised to check the data-sheet for your particular + multi-character 7-segment display. The pin-outs of these displays vary + significantly and are very likely to be different to that shown on the + breadboard above. For this reason, the schematic for this circuit is + provided below; adapt it to your particular display. + +.. image:: images/7seg_multi_schem.* + +The following code can be used to scroll a message across the display: + +.. literalinclude:: examples/multichar_scroll.py + + +Who's home indicator +==================== + +Using a number of green-red LED pairs, you can show the status of who's home, +according to which IP addresses you can ping successfully. Note that this +assumes each person's mobile phone has a reserved IP address on the home router. + +.. literalinclude:: examples/whos_home_leds.py + +Alternatively, using the `STATUS Zero`_ board: + +.. literalinclude:: examples/whos_home_status.py + +Travis build LED indicator +========================== + +Use LEDs to indicate the status of a Travis build. A green light means the +tests are passing, a red light means the build is broken: + +.. literalinclude:: examples/led_travis.py + +Note this recipe requires `travispy`_. Install with ``sudo pip3 install +travispy``. + +Button controlled robot +======================= + +Alternatively to the examples in the simple recipes, you can use four buttons +to program the directions and add a fifth button to process them in turn, like +a Bee-Bot or Turtle robot. + +.. literalinclude:: examples/robot_buttons_2.py + +Robot controlled by 2 potentiometers +==================================== + +Use two potentiometers to control the left and right motor speed of a robot: + +.. literalinclude:: examples/robot_pots_1.py + +To include reverse direction, scale the potentiometer values from 0->1 to -1->1: + +.. literalinclude:: examples/robot_pots_2.py + +.. note:: + + Please note the example above requires Python 3. In Python 2, :func:`zip` + doesn't support lazy evaluation so the script will simply hang. + +BlueDot LED +=========== + +BlueDot is a Python library an Android app which allows you to easily add +Bluetooth control to your Raspberry Pi project. A simple example to control a +LED using the BlueDot app: + +.. literalinclude:: examples/bluedot_led.py + +Note this recipe requires ``bluedot`` and the associated Android app. See the +`BlueDot documentation`_ for installation instructions. + +BlueDot robot +============= + +You can create a Bluetooth controlled robot which moves forward when the dot is +pressed and stops when it is released: + +.. literalinclude:: examples/bluedot_robot_1.py + +Or a more advanced example including controlling the robot's speed and precise +direction: + +.. literalinclude:: examples/bluedot_robot_2.py + +Controlling the Pi's own LEDs +============================= + +On certain models of Pi (specifically the model A+, B+, and 2B) it's possible +to control the power and activity LEDs. This can be useful for testing GPIO +functionality without the need to wire up your own LEDs (also useful because +the power and activity LEDs are "known good"). + +Firstly you need to disable the usual triggers for the built-in LEDs. This can +be done from the terminal with the following commands: + +.. code-block:: console + + $ echo none | sudo tee /sys/class/leds/led0/trigger + $ echo gpio | sudo tee /sys/class/leds/led1/trigger + +Now you can control the LEDs with gpiozero like so: + +.. literalinclude:: examples/led_builtin.py + +To revert the LEDs to their usual purpose you can either reboot your Pi or +run the following commands: + +.. code-block:: console + + $ echo mmc0 | sudo tee /sys/class/leds/led0/trigger + $ echo input | sudo tee /sys/class/leds/led1/trigger + +.. note:: + + On the Pi Zero you can control the activity LED with this recipe, but + there's no separate power LED to control (it's also worth noting the + activity LED is active low, so set ``active_high=False`` when constructing + your LED component). + + On the original Pi 1 (model A or B), the activity LED can be controlled + with GPIO16 (after disabling its trigger as above) but the power LED is + hard-wired on. + + On the Pi 3 the LEDs are controlled by a GPIO expander which is not + accessible from gpiozero (yet). + + +.. _travispy: https://travispy.readthedocs.io/ +.. _STATUS Zero: https://thepihut.com/status +.. _BlueDot documentation: https://bluedot.readthedocs.io/en/latest/index.html diff --git a/docs/recipes_remote_gpio.rst b/docs/recipes_remote_gpio.rst new file mode 100644 index 000000000..84f7e1cf8 --- /dev/null +++ b/docs/recipes_remote_gpio.rst @@ -0,0 +1,85 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2019-2023 Dave Jones +.. Copyright (c) 2017 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +=================== +Remote GPIO Recipes +=================== + +.. currentmodule:: gpiozero + +The following recipes demonstrate some of the capabilities of the remote GPIO +feature of the GPIO Zero library. Before you start following these examples, +please read up on preparing your Pi and your host PC to work with +:doc:`remote_gpio`. + +Please note that all recipes are written assuming Python 3. Recipes *may* work +under Python 2, but no guarantees! + + +LED + Button +============ + +Let a :class:`Button` on one Raspberry Pi control the :class:`LED` of another: + +.. literalinclude:: examples/led_button_remote_1.py + + +LED + 2 Buttons +=============== + +The :class:`LED` will come on when both buttons are pressed: + +.. literalinclude:: examples/led_button_remote_2.py + + +Multi-room motion alert +======================= + +Install a Raspberry Pi with a :class:`MotionSensor` in each room of your house, +and have an :class:`LED` indicator showing when there's motion in each room: + +.. literalinclude:: examples/multi_room_motion_alert.py + + +Multi-room doorbell +=================== + +Install a Raspberry Pi with a :class:`Buzzer` attached in each room you want to +hear the doorbell, and use a push :class:`Button` as the doorbell: + +.. literalinclude:: examples/multi_room_doorbell.py + +This could also be used as an internal doorbell (tell people it's time for +dinner from the kitchen). + + +Remote button robot +=================== + +Similarly to the simple recipe for the button controlled :class:`Robot`, this +example uses four buttons to control the direction of a robot. However, using +remote pins for the robot means the control buttons can be separate from the +robot: + +.. literalinclude:: examples/remote_button_robot.py + + +Light sensor + Sense HAT +========================= + +The `Sense HAT`_ (not supported by GPIO Zero) includes temperature, humidity +and pressure sensors, but no light sensor. Remote GPIO allows an external +:class:`LightSensor` to be used as well. The Sense HAT LED display can be used +to show different colours according to the light levels: + +.. literalinclude:: examples/sense_hat_remote_2.py + +Note that in this case, the Sense HAT code must be run locally, and the GPIO +remotely. + + +.. _Sense HAT: https://www.raspberrypi.com/products/sense-hat/ diff --git a/docs/remote_gpio.rst b/docs/remote_gpio.rst new file mode 100644 index 000000000..0c3e78a2d --- /dev/null +++ b/docs/remote_gpio.rst @@ -0,0 +1,353 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2017-2021 Ben Nuttall +.. Copyright (c) 2017 rgm +.. +.. SPDX-License-Identifier: BSD-3-Clause + +======================= +Configuring Remote GPIO +======================= + +.. currentmodule:: gpiozero + +GPIO Zero supports a number of different pin implementations (low-level pin +libraries which deal with the GPIO pins directly). By default, the `RPi.GPIO`_ +library is used (assuming it is installed on your system), but you can +optionally specify one to use. For more information, see the :doc:`api_pins` +documentation page. + +One of the pin libraries supported, `pigpio`_, provides the ability to control +GPIO pins remotely over the network, which means you can use GPIO Zero to +control devices connected to a Raspberry Pi on the network. You can do this +from another Raspberry Pi, or even from a PC. + +See the :doc:`recipes_remote_gpio` page for examples on how remote pins can be +used. + + +Preparing the Raspberry Pi +========================== + +If you're using Raspberry Pi OS (desktop - not Lite) then you have everything +you need to use the remote GPIO feature. If you're using Raspberry Pi OS Lite, +or another distribution, you'll need to install pigpio: + +.. code-block:: console + + $ sudo apt install pigpio + +Alternatively, pigpio is available from `abyz.me.uk`_. + +You'll need to enable remote connections, and launch the pigpio daemon on the +Raspberry Pi. + + +Enable remote connections +------------------------- + +On the Raspberry Pi OS desktop image, you can enable *Remote GPIO* in the +Raspberry Pi configuration tool: + +.. image:: images/raspi-config.png + :align: center + :width: 514px + +Alternatively, enter ``sudo raspi-config`` on the command line, and enable +Remote GPIO. This is functionally equivalent to the desktop method. + +This will allow remote connections (until disabled) when the pigpio daemon is +launched using :command:`systemctl` (see below). It will also launch the pigpio +daemon for the current session. Therefore, nothing further is required for the +current session, but after a reboot, a :command:`systemctl` command will be +required. + + +Command-line: systemctl +----------------------- + +To automate running the daemon at boot time, run: + +.. code-block:: console + + $ sudo systemctl enable pigpiod + +To run the daemon once using :command:`systemctl`, run: + +.. code-block:: console + + $ sudo systemctl start pigpiod + + +Command-line: pigpiod +--------------------- + +Another option is to launch the pigpio daemon manually: + +.. code-block:: console + + $ sudo pigpiod + +This is for single-session-use and will not persist after a reboot. However, +this method can be used to allow connections from a specific IP address, using +the ``-n`` flag. For example: + +.. code-block:: console + + $ sudo pigpiod -n localhost # allow localhost only + $ sudo pigpiod -n 192.168.1.65 # allow 192.168.1.65 only + $ sudo pigpiod -n localhost -n 192.168.1.65 # allow localhost and 192.168.1.65 only + +.. note:: + + Note that running ``sudo pigpiod`` will not honour the Remote GPIO + configuration setting (i.e. without the ``-n`` flag it will allow remote + connections even if the remote setting is disabled), but ``sudo systemctl + enable pigpiod`` or ``sudo systemctl start pigpiod`` will not allow remote + connections unless configured accordingly. + + +Preparing the control computer +============================== + +If the control computer (the computer you're running your Python code from) is a +Raspberry Pi running Raspberry Pi OS (or a PC running `Raspberry Pi Desktop +x86`_), then you have everything you need. If you're using another Linux +distribution, Mac OS or Windows then you'll need to install the `pigpio`_ Python +library on the PC. + + +Raspberry Pi +------------ + +First, update your repositories list: + +.. code-block:: console + + $ sudo apt update + +Then install GPIO Zero and the pigpio library for Python 3: + +.. code-block:: console + + $ sudo apt install python3-gpiozero python3-pigpio + +or Python 2: + +.. code-block:: console + + $ sudo apt install python-gpiozero python-pigpio + +Alternatively, install with pip: + +.. code-block:: console + + $ sudo pip3 install gpiozero pigpio + +or for Python 2: + +.. code-block:: console + + $ sudo pip install gpiozero pigpio + + +Linux +----- + +First, update your distribution's repositories list. For example: + +.. code-block:: console + + $ sudo apt update + +Then install pip for Python 3: + +.. code-block:: console + + $ sudo apt install python3-pip + +or Python 2: + +.. code-block:: console + + $ sudo apt install python-pip + +(Alternatively, install pip with `get-pip`_.) + +Next, install GPIO Zero and pigpio for Python 3: + +.. code-block:: console + + $ sudo pip3 install gpiozero pigpio + +or Python 2: + +.. code-block:: console + + $ sudo pip install gpiozero pigpio + + +Mac OS +------ + +First, install pip. If you installed Python 3 using brew, you will already have +pip. If not, install pip with `get-pip`_. + +Next, install GPIO Zero and pigpio with pip: + +.. code-block:: console + + $ pip3 install gpiozero pigpio + +Or for Python 2: + +.. code-block:: console + + $ pip install gpiozero pigpio + + +Windows +------- + +Modern Python installers for Windows bundle pip with Python. If pip is not +installed, you can `follow this guide`_. Next, install GPIO Zero and pigpio with +pip: + +.. code-block:: doscon + + C:\Users\user1> pip install gpiozero pigpio + + +Environment variables +===================== + +The simplest way to use devices with remote pins is to set the +:envvar:`PIGPIO_ADDR` environment variable to the IP address of the desired +Raspberry Pi. You must run your Python script or launch your development +environment with the environment variable set using the command line. For +example, one of the following: + +.. code-block:: console + + $ PIGPIO_ADDR=192.168.1.3 python3 hello.py + $ PIGPIO_ADDR=192.168.1.3 python3 + $ PIGPIO_ADDR=192.168.1.3 ipython3 + $ PIGPIO_ADDR=192.168.1.3 idle3 & + +If you are running this from a PC (not a Raspberry Pi) with gpiozero and the +`pigpio`_ Python library installed, this will work with no further +configuration. However, if you are running this from a Raspberry Pi, you will +also need to ensure the default pin factory is set to +:class:`~gpiozero.pins.pigpio.PiGPIOFactory`. If `RPi.GPIO`_ is installed, +this will be selected as the default pin factory, so either uninstall it, or +use the :envvar:`GPIOZERO_PIN_FACTORY` environment variable to override it: + +.. code-block:: console + + $ GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=192.168.1.3 python3 hello.py + +This usage will set the pin factory to +:class:`~gpiozero.pins.pigpio.PiGPIOFactory` with a default host of +``192.168.1.3``. The pin factory can be changed inline in the code, as seen in +the following sections. + +With this usage, you can write gpiozero code like you would on a Raspberry Pi, +with no modifications needed. For example: + +.. literalinclude:: examples/led_1.py + +When run with: + +.. code-block:: console + + $ PIGPIO_ADDR=192.168.1.3 python3 led.py + +will flash the LED connected to pin 17 of the Raspberry Pi with the IP address +``192.168.1.3``. And: + +.. code-block:: console + + $ PIGPIO_ADDR=192.168.1.4 python3 led.py + +will flash the LED connected to pin 17 of the Raspberry Pi with the IP address +``192.168.1.4``, without any code changes, as long as the Raspberry Pi has the +pigpio daemon running. + +.. note:: + + When running code directly on a Raspberry Pi, any pin factory can be used + (assuming the relevant library is installed), but when a device is used + remotely, only :class:`~gpiozero.pins.pigpio.PiGPIOFactory` can be used, as + `pigpio`_ is the only pin library which supports remote GPIO. + + +Pin factories +============= + +An alternative (or additional) method of configuring gpiozero objects to use +remote pins is to create instances of +:class:`~gpiozero.pins.pigpio.PiGPIOFactory` objects, and use them when +instantiating device objects. For example, with no environment variables set: + +.. literalinclude:: examples/led_remote_1.py + +This allows devices on multiple Raspberry Pis to be used in the same script: + +.. literalinclude:: examples/led_remote_2.py + +You can, of course, continue to create gpiozero device objects as normal, and +create others using remote pins. For example, if run on a Raspberry Pi, the +following script will flash an LED on the controller Pi, and also on another Pi +on the network: + +.. literalinclude:: examples/led_remote_3.py + +Alternatively, when run with the environment variables +``GPIOZERO_PIN_FACTORY=pigpio PIGPIO_ADDR=192.168.1.3`` set, the following +script will behave exactly the same as the previous one: + +.. literalinclude:: examples/led_remote_4.py + +Of course, multiple IP addresses can be used: + +.. literalinclude:: examples/led_remote_5.py + +Note that these examples use the :class:`LED` class, which takes a *pin* +argument to initialise. Some classes, particularly those representing HATs and +other add-on boards, do not require their pin numbers to be specified. However, +it is still possible to use remote pins with these devices, either using +environment variables, or the *pin_factory* keyword argument: + +.. literalinclude:: examples/traffichat_remote_1.py + +This also allows you to swap between two IP addresses and create instances of +multiple HATs connected to different Pis: + +.. literalinclude:: examples/traffichat_remote_2.py + +You could even use a HAT which is not supported by GPIO Zero (such as the +`Sense HAT`_) on one Pi, and use remote pins to control another over the +network: + +.. literalinclude:: examples/sense_hat_remote.py + +Note that in this case, the Sense HAT code must be run locally, and the GPIO +remotely. + + +Remote GPIO usage +================= + +Continue to: + +* :doc:`recipes_remote_gpio` +* :doc:`pi_zero_otg` + +.. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO +.. _pigpio: http://abyz.me.uk/rpi/pigpio/python.html +.. _abyz.me.uk: http://abyz.me.uk/rpi/pigpio/download.html +.. _Raspberry Pi Desktop x86: https://www.raspberrypi.com/software/raspberry-pi-desktop/ +.. _get-pip: https://pip.pypa.io/en/stable/installing/ +.. _follow this guide: https://projects.raspberrypi.org/en/projects/using-pip-on-windows +.. _Sense HAT: https://www.raspberrypi.com/products/sense-hat/ diff --git a/docs/source_values.rst b/docs/source_values.rst new file mode 100644 index 000000000..af15c35d1 --- /dev/null +++ b/docs/source_values.rst @@ -0,0 +1,287 @@ +.. GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +.. +.. Copyright (c) 2017-2023 Dave Jones +.. Copyright (c) 2016-2019 Ben Nuttall +.. +.. SPDX-License-Identifier: BSD-3-Clause + +============= +Source/Values +============= + +.. currentmodule:: gpiozero + +GPIO Zero provides a method of using the declarative programming paradigm to +connect devices together: feeding the values of one device into another, for +example the values of a button into an LED: + +.. image:: images/source_values/led_button.* + :align: center + +.. literalinclude:: examples/led_button.py + +which is equivalent to: + +.. literalinclude:: examples/led_button_loop.py + +except that the former is updated in a background thread, which enables you to +do other things at the same time. + +Every device has a :attr:`~Device.value` property (the device's current value). +Input devices (like buttons) can only have their values read, but output devices +(like LEDs) can also have their value set to alter the state of the device: + +.. code-block:: pycon + + >>> led = PWMLED(17) + >>> led.value # LED is initially off + 0.0 + >>> led.on() # LED is now on + >>> led.value + 1.0 + >>> led.value = 0 # LED is now off + +Every device also has a :attr:`~ValuesMixin.values` property (a `generator`_ +continuously yielding the device's current value). All output devices have a +:attr:`~SourceMixin.source` property which can be set to any `iterator`_. The +device will iterate over the values of the device provided, setting the device's +value to each element at a rate specified in the +:attr:`~SourceMixin.source_delay` property (the default is 0.01 seconds). + +.. _generator: https://wiki.python.org/moin/Generators +.. _iterator: https://wiki.python.org/moin/Iterator + +.. image:: images/source_values/source_values.* + :align: center + +The most common use case for this is to set the source of an output device to +match the values of an input device, like the example above. A more interesting +example would be a potentiometer controlling the brightness of an LED: + +.. image:: images/source_values/pwmled_pot.* + :align: center + +.. literalinclude:: examples/pwmled_pot.py + +The way this works is that the input device's :attr:`~ValuesMixin.values` +property is used to feed values into the output device. Prior to v1.5, the +:attr:`~SourceMixin.source` had to be set directly to a device's +:attr:`~ValuesMixin.values` property: + +.. literalinclude:: examples/pwmled_pot_values.py + +.. note:: + + Although this method is still supported, the recommended way is now to set + the :attr:`~SourceMixin.source` to a device object. + +It is also possible to set an output device's :attr:`~SourceMixin.source` to +another output device, to keep them matching. In this example, the red LED is +set to match the button, and the green LED is set to match the red LED, so both +LEDs will be on when the button is pressed: + +.. image:: images/source_values/matching_leds.* + :align: center + +.. literalinclude:: examples/matching_leds.py + +Processing values +----------------- + +The device's values can also be processed before they are passed to the +:attr:`~SourceMixin.source`: + +.. image:: images/source_values/value_processing.* + :align: center + +For example, writing a generator function to pass the opposite of the Button +value into the LED: + +.. image:: images/source_values/led_button_opposite.* + :align: center + +.. literalinclude:: examples/source_value_processing.py + +Alternatively, a custom generator can be used to provide values from an +artificial source: + +.. image:: images/source_values/custom_generator.* + :align: center + +For example, writing a generator function to randomly yield 0 or 1: + +.. image:: images/source_values/random_led.* + :align: center + +.. literalinclude:: examples/custom_generator.py + +If the iterator is infinite (i.e. an infinite generator), the elements will be +processed until the :attr:`~SourceMixin.source` is changed or set to +:data:`None`. + +If the iterator is finite (e.g. a list), this will terminate once all elements +are processed (leaving the device's value at the final element): + +.. literalinclude:: examples/custom_generator_finite.py + +Source Tools +------------ + +GPIO Zero provides a set of ready-made functions for dealing with +source/values, called source tools. These are available by importing from +:mod:`gpiozero.tools`. + +Some of these source tools are artificial sources which require no input: + +.. image:: images/source_values/source_tool.* + :align: center + +In this example, random values between 0 and 1 are passed to the LED, giving it +a flickering candle effect: + +.. image:: images/source_values/source_tool_candle.* + :align: center + +.. literalinclude:: examples/random_values.py + +Note that in the above example, :attr:`~SourceMixin.source_delay` is used to +make the LED iterate over the random values slightly slower. +:attr:`~SourceMixin.source_delay` can be set to a larger number (e.g. 1 for a +one second delay) or set to 0 to disable any delay. + +Some tools take a single source and process its values: + +.. image:: images/source_values/source_tool_value_processor.* + :align: center + +In this example, the LED is lit only when the button is not pressed: + +.. image:: images/source_values/led_button_negated.* + :align: center + +.. literalinclude:: examples/negated.py + +.. note:: + + Note that source tools which take one or more ``value`` parameters support + passing either :class:`~ValuesMixin` derivatives, or iterators, including a + device's :attr:`~ValuesMixin.values` property. + +Some tools combine the values of multiple sources: + +.. image:: images/source_values/combining_sources.* + :align: center + +In this example, the LED is lit only if both buttons are pressed (like an +`AND`_ gate): + +.. image:: images/source_values/combining_sources_led_buttons.* + :align: center + +.. literalinclude:: examples/combining_sources.py + +Similarly, :func:`~tools.any_values` with two buttons would simulate an `OR`_ +gate. + +.. _AND: https://en.wikipedia.org/wiki/AND_gate +.. _OR: https://en.wikipedia.org/wiki/OR_gate + +While most devices have a :attr:`~Device.value` range between 0 and 1, some have +a range between -1 and 1 (e.g. :class:`Motor`, :class:`Servo` and +:class:`TonalBuzzer`). Some source tools output values between -1 and 1, which +are ideal for these devices, for example passing :func:`~tools.sin_values` in: + +.. image:: images/source_values/sin_values.* + :align: center + +.. literalinclude:: examples/sin_values.py + +In this example, all three devices are following the `sine wave`_. The motor +value ramps up from 0 (stopped) to 1 (full speed forwards), then back down to 0 +and on to -1 (full speed backwards) in a cycle. Similarly, the servo moves from +its mid point to the right, then towards the left; and the buzzer starts with +its mid tone, gradually raises its frequency, to its highest tone, then down +towards its lowest tone. Note that setting :attr:`~SourceMixin.source_delay` +will alter the speed at which the device iterates through the values. +Alternatively, the tool :func:`~tools.cos_values` could be used to start from -1 +and go up to 1, and so on. + +.. _sine wave: https://en.wikipedia.org/wiki/Sine_wave + +Internal devices +---------------- + +GPIO Zero also provides several :doc:`internal devices ` which +represent facilities provided by the operating system itself. These can be used +to react to things like the time of day, or whether a server is available on the +network. These classes include a :attr:`~ValuesMixin.values` property which can +be used to feed values into a device's :attr:`~SourceMixin.source`. For example, +a lamp connected to an :class:`Energenie` socket can be controlled by a +:class:`TimeOfDay` object so that it is on between the hours of 8am and 8pm: + +.. image:: images/source_values/timed_heat_lamp.* + :align: center + +.. literalinclude:: examples/timed_heat_lamp.py + +Using the :class:`DiskUsage` class with :class:`LEDBarGraph` can show your Pi's +disk usage percentage on a bar graph: + +.. image:: images/source_values/disk_usage_bar_graph.* + :align: center + +.. literalinclude:: examples/disk_usage_bar_graph.py + +Demonstrating a garden light system whereby the light comes on if it's dark and +there's motion is simple enough, but it requires using the +:func:`~tools.booleanized` source tool to convert the light sensor from a float +value into a boolean: + +.. image:: images/source_values/garden_light.* + :align: center + +.. literalinclude:: examples/garden_light.py + +Composite devices +----------------- + +The :attr:`~Device.value` of a composite device made up of the nested values of +its devices. For example, the value of a :class:`Robot` object is a 2-tuple +containing its left and right motor values: + +.. code-block:: pycon + + >>> from gpiozero import Robot + >>> robot = Robot(left=(14, 15), right=(17, 18)) + >>> robot.value + RobotValue(left_motor=0.0, right_motor=0.0) + >>> tuple(robot.value) + (0.0, 0.0) + >>> robot.forward() + >>> tuple(robot.value) + (1.0, 1.0) + >>> robot.backward() + >>> tuple(robot.value) + (-1.0, -1.0) + >>> robot.value = (1, 1) # robot is now driven forwards + +Use two potentiometers to control the left and right motor speed of a robot: + +.. image:: images/source_values/robot_pots_1.* + :align: center + +.. literalinclude:: examples/robot_pots_1.py + +To include reverse direction, scale the potentiometer values from 0->1 to -1->1: + +.. image:: images/source_values/robot_pots_2.* + :align: center + +.. literalinclude:: examples/robot_pots_2.py + +Note that this example uses the built-in :func:`zip` rather than the tool +:func:`~tools.zip_values` as the :func:`~tools.scaled` tool yields values which +do not need converting, just zipping. Also note that this use of :func:`zip` +will not work in Python 2, instead use `izip`_. + +.. _izip: https://docs.python.org/2/library/itertools.html#itertools.izip diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index 950823e15..b318d66ca 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -1,42 +1,128 @@ -from __future__ import absolute_import - -import atexit - -from RPi import GPIO +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2023 Dave Jones +# Copyright (c) 2015-2021 Ben Nuttall +# Copyright (c) 2019 tuftii <3215045+tuftii@users.noreply.github.com> +# Copyright (c) 2019 Jeevan M R <14.jeevan@gmail.com> +# Copyright (c) 2019 ForToffee +# Copyright (c) 2018 Claire Pollard +# Copyright (c) 2016 pcopa +# Copyright (c) 2016 Ian Harcombe +# Copyright (c) 2016 Andrew Scheller +# Copyright (c) 2016 Andrew Scheller +# Copyright (c) 2015 Philip Howard +# +# 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 ( - _gpio_threads_shutdown, - GPIODeviceError, + Device, GPIODevice, + CompositeDevice, +) +from .mixins import ( + SharedMixin, + SourceMixin, + ValuesMixin, + EventsMixin, + event, + HoldMixin, ) from .input_devices import ( - InputDeviceError, InputDevice, + DigitalInputDevice, + SmoothedInputDevice, Button, + LineSensor, MotionSensor, LightSensor, - TemperatureSensor, + 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 ( - TrafficLights + CompositeOutputDevice, + ButtonBoard, + LEDCollection, + LEDBoard, + LEDBarGraph, + LEDCharDisplay, + LEDMultiCharDisplay, + LEDCharFont, + LedBorg, + PiHutXmasTree, + PiLiter, + PiLiterBarGraph, + TrafficLights, PiTraffic, + PiStop, + StatusZero, + StatusBoard, + SnowPi, + TrafficLightsBuzzer, FishDish, - PiLiter, + TrafficHat, + TrafficpHat, + Robot, + RyanteckRobot, + CamJamKitRobot, + PololuDRV8835Robot, + PhaseEnableRobot, + Energenie, + PumpkinPi, + JamHat, + Pibrella, +) +from .internal_devices import ( + InternalDevice, + PolledInternalDevice, + PingServer, + CPUTemperature, + LoadAverage, + TimeOfDay, + DiskUsage, ) - - -def gpiozero_shutdown(): - _gpio_threads_shutdown() - GPIO.cleanup() - -atexit.register(gpiozero_shutdown) -GPIO.setmode(GPIO.BCM) -GPIO.setwarnings(False) - -__version__ = '0.2.0' diff --git a/gpiozero/boards.py b/gpiozero/boards.py index ebdd7ae78..7abc9f4f1 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -1,61 +1,2794 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2024 Dave Jones +# Copyright (c) 2016-2022 Andrew Scheller +# Copyright (c) 2015-2021 Ben Nuttall +# Copyright (c) 2020 Ryan Walmsley +# Copyright (c) 2020 Jack Wearden +# Copyright (c) 2019 tuftii <3215045+tuftii@users.noreply.github.com> +# Copyright (c) 2019 ForToffee +# Copyright (c) 2018 SteveAmor +# Copyright (c) 2018 Rick Ansell +# Copyright (c) 2018 Claire Pollard +# Copyright (c) 2016 Ian Harcombe +# Copyright (c) 2016 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import warnings +from time import sleep +from itertools import repeat, cycle, chain, tee +from threading import Lock +from collections import OrderedDict, Counter, namedtuple +from collections.abc import MutableMapping +from pprint import pformat + +# Remove the try clause when 3.7 support is no longer trivial +try: + import importlib_resources as resources +except ImportError: + from importlib import resources + +from .exc import ( + DeviceClosed, + PinInvalidPin, + GPIOPinMissing, + EnergenieSocketMissing, + EnergenieBadSocket, + OutputDeviceBadValue, + CompositeDeviceBadDevice, + BadWaitTime, + ) from .input_devices import Button -from .output_devices import LED -from .devices import GPIODeviceError +from .output_devices import ( + OutputDevice, + LED, + PWMLED, + RGBLED, + Buzzer, + Motor, + PhaseEnableMotor, + TonalBuzzer, + ) +from .threads import GPIOThread +from .devices import Device, CompositeDevice +from .mixins import SharedMixin, SourceMixin, HoldMixin, event +from .fonts import load_font_7seg, load_font_14seg + + +def pairwise(it): + a, b = tee(it) + next(b, None) + return zip(a, b) + + +class CompositeOutputDevice(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` with :meth:`on`, :meth:`off`, and + :meth:`toggle` methods for controlling subordinate output devices. Also + extends :attr:`value` to be writeable. + + :param Device \\*args: + The un-named devices that belong to the composite device. The + :attr:`~Device.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 on(self): + """ + Turn all the output devices on. + """ + for device in self: + if isinstance(device, (OutputDevice, CompositeOutputDevice)): + device.on() + + def off(self): + """ + Turn all the output devices off. + """ + for device in self: + if isinstance(device, (OutputDevice, CompositeOutputDevice)): + device.off() + + def toggle(self): + """ + Toggle all the output devices. For each device, if it's on, turn it + off; if it's off, turn it on. + """ + for device in self: + if isinstance(device, (OutputDevice, CompositeOutputDevice)): + device.toggle() + + @property + def value(self): + """ + A tuple containing a value for each subordinate device. This property + can also be set to update the state of all subordinate output devices. + """ + return super().value + + @value.setter + def value(self, value): + for device, v in zip(self, value): + if isinstance(device, (OutputDevice, CompositeOutputDevice)): + device.value = v + # Simply ignore values for non-output devices + + +class ButtonBoard(HoldMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a generic button board or + collection of buttons. The :attr:`value` of the button board is a tuple + of all the buttons states. This can be used to control all the LEDs in a + :class:`LEDBoard` with a :class:`ButtonBoard`:: + + from gpiozero import LEDBoard, ButtonBoard + from signal import pause + + leds = LEDBoard(2, 3, 4, 5) + btns = ButtonBoard(6, 7, 8, 9) + leds.source = btns + + pause() + + Alternatively you could represent the number of pressed buttons with an + :class:`LEDBarGraph`:: + + from gpiozero import LEDBarGraph, ButtonBoard + from statistics import mean + from signal import pause + + graph = LEDBarGraph(2, 3, 4, 5) + bb = ButtonBoard(6, 7, 8, 9) + graph.source = (mean(values) for values in bb.values) + + pause() + + :type pins: int or str + :param \\*pins: + Specify the GPIO pins that the buttons of the board are attached to. + See :ref:`pin-numbering` for valid pin numbers. You can designate as + many pins as necessary. + + :type pull_up: bool or None + :param pull_up: + If :data:`True` (the default), the GPIO pins will be pulled high by + default. In this case, connect the other side of the buttons to + ground. If :data:`False`, the GPIO pins will be pulled low by default. + In this case, connect the other side of the buttons to 3V3. If + :data:`None`, the pin will be floating, so it must be externally pulled + up or down and the ``active_state`` parameter must be set accordingly. + + :type active_state: bool or None + :param active_state: + See description under :class:`InputDevice` for more information. + + :param float bounce_time: + If :data:`None` (the default), no software bounce compensation will be + performed. Otherwise, this is the length of time (in seconds) that the + buttons will ignore changes in state after an initial change. + + :param float hold_time: + The length of time (in seconds) to wait after any button is pushed, + until executing the :attr:`when_held` handler. Defaults to ``1``. + + :param bool hold_repeat: + If :data:`True`, the :attr:`when_held` handler will be repeatedly + executed as long as any buttons remain held, every *hold_time* seconds. + If :data:`False` (the default) the :attr:`when_held` handler will be + only be executed once per hold. + + :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). + + :type named_pins: int or str + :param \\*\\*named_pins: + Specify GPIO pins that buttons of the board are attached to, + associating each button with a property name. You can designate as + many pins as necessary and use any names, provided they're not already + in use by something else. + """ + def __init__(self, *pins, pull_up=True, active_state=None, + bounce_time=None, hold_time=1, hold_repeat=False, + _order=None, pin_factory=None, **named_pins): + super().__init__( + *( + Button(pin, pull_up=pull_up, active_state=active_state, + bounce_time=bounce_time, hold_time=hold_time, + hold_repeat=hold_repeat, pin_factory=pin_factory) + for pin in pins + ), + _order=_order, + pin_factory=pin_factory, + **{ + name: Button(pin, pull_up=pull_up, active_state=active_state, + bounce_time=bounce_time, hold_time=hold_time, + hold_repeat=hold_repeat, pin_factory=pin_factory) + for name, pin in named_pins.items() + } + ) + if len(self) == 0: + raise GPIOPinMissing('No pins given') + def get_new_handler(device): + def fire_both_events(ticks, state): + device._fire_events(ticks, device._state_to_value(state)) + self._fire_events(ticks, self.is_active) + return fire_both_events + # _handlers only exists to ensure that we keep a reference to the + # generated fire_both_events handler for each Button (remember that + # pin.when_changed only keeps a weak reference to handlers) + self._handlers = tuple(get_new_handler(device) for device in self) + for button, handler in zip(self, self._handlers): + button.pin.when_changed = handler + self._when_changed = None + self._last_value = None + # Call _fire_events once to set initial state of events + self._fire_events(self.pin_factory.ticks(), self.is_active) + self.hold_time = hold_time + self.hold_repeat = hold_repeat + + @property + def pull_up(self): + """ + If :data:`True`, the device uses a pull-up resistor to set the GPIO pin + "high" by default. + """ + return self[0].pull_up + + when_changed = event() + + def _fire_changed(self): + if self.when_changed: + self.when_changed() + + def _fire_events(self, ticks, new_value): + super()._fire_events(ticks, new_value) + old_value, self._last_value = self._last_value, new_value + if old_value is None: + # Initial "indeterminate" value; don't do anything + pass + elif old_value != new_value: + self._fire_changed() + +ButtonBoard.is_pressed = ButtonBoard.is_active +ButtonBoard.pressed_time = ButtonBoard.active_time +ButtonBoard.when_pressed = ButtonBoard.when_activated +ButtonBoard.when_released = ButtonBoard.when_deactivated +ButtonBoard.wait_for_press = ButtonBoard.wait_for_active +ButtonBoard.wait_for_release = ButtonBoard.wait_for_inactive + + +class LEDCollection(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice`. Abstract base class for + :class:`LEDBoard` and :class:`LEDBarGraph`. + """ + def __init__(self, *pins, pwm=False, active_high=True, initial_value=False, + _order=None, pin_factory=None, **named_pins): + LEDClass = PWMLED if pwm else LED + super().__init__( + *( + pin_or_collection + if isinstance(pin_or_collection, LEDCollection) else + LEDClass( + pin_or_collection, active_high=active_high, + initial_value=initial_value, pin_factory=pin_factory + ) + for pin_or_collection in pins + ), + _order=_order, + pin_factory=pin_factory, + **{ + name: pin_or_collection + if isinstance(pin_or_collection, LEDCollection) else + LEDClass( + pin_or_collection, active_high=active_high, + initial_value=initial_value, pin_factory=pin_factory + ) + for name, pin_or_collection in named_pins.items() + } + ) + if len(self) == 0: + raise GPIOPinMissing('No pins given') + leds = [] + for item in self: + if isinstance(item, LEDCollection): + for subitem in item.leds: + leds.append(subitem) + else: + leds.append(item) + self._leds = tuple(leds) + + @property + def leds(self): + """ + A flat tuple of all LEDs contained in this collection (and all + sub-collections). + """ + return self._leds + + @property + def active_high(self): + return self[0].active_high + + +LEDCollection.is_lit = LEDCollection.is_active + + +class LEDBoard(LEDCollection): + """ + Extends :class:`LEDCollection` and represents a generic LED board or + collection of LEDs. + + The following example turns on all the LEDs on a board containing 5 LEDs + attached to GPIO pins 2 through 6:: + + from gpiozero import LEDBoard + + leds = LEDBoard(2, 3, 4, 5, 6) + leds.on() + + :type pins: int or str or LEDCollection + :param \\*pins: + Specify the GPIO pins that the LEDs of the board are attached to. See + :ref:`pin-numbering` for valid pin numbers. You can designate as many + pins as necessary. You can also specify :class:`LEDBoard` instances to + create trees of LEDs. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances for each pin. If + :data:`False` (the default), construct regular :class:`LED` instances. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set all the + associated pins to HIGH. If :data:`False`, the :meth:`on` method will + set all pins to LOW (the :meth:`off` method always does the opposite). + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + :type named_pins: int or str + :param \\*\\*named_pins: + Specify GPIO pins that LEDs of the board are attached to, associating + each LED with a property name. You can designate as many pins as + necessary and use any names, provided they're not already in use by + something else. You can also specify :class:`LEDBoard` instances to + create trees of LEDs. + """ + def __init__(self, *pins, pwm=False, active_high=True, initial_value=False, + _order=None, pin_factory=None, **named_pins): + self._blink_thread = None + self._blink_leds = [] + self._blink_lock = Lock() + super().__init__(*pins, pwm=pwm, active_high=active_high, + initial_value=initial_value, _order=_order, + pin_factory=pin_factory, **named_pins) + + def close(self): + try: + self._stop_blink() + except AttributeError: + pass + super().close() + + def on(self, *args): + """ + If no arguments are specified, turn all the LEDs on. If arguments are + specified, they must be the indexes of the LEDs you wish to turn on. + For example:: + + from gpiozero import LEDBoard + + leds = LEDBoard(2, 3, 4, 5) + leds.on(0) # turn on the first LED (pin 2) + leds.on(-1) # turn on the last LED (pin 5) + leds.on(1, 2) # turn on the middle LEDs (pins 3 and 4) + leds.off() # turn off all LEDs + leds.on() # turn on all LEDs + + If :meth:`blink` is currently active, it will be stopped first. + + :param int args: + The index(es) of the LED(s) to turn on. If no indexes are specified + turn on all LEDs. + """ + self._stop_blink() + if args: + for index in args: + self[index].on() + else: + super().on() + + def off(self, *args): + """ + If no arguments are specified, turn all the LEDs off. If arguments are + specified, they must be the indexes of the LEDs you wish to turn off. + For example:: + + from gpiozero import LEDBoard + + leds = LEDBoard(2, 3, 4, 5) + leds.on() # turn on all LEDs + leds.off(0) # turn off the first LED (pin 2) + leds.off(-1) # turn off the last LED (pin 5) + leds.off(1, 2) # turn off the middle LEDs (pins 3 and 4) + leds.on() # turn on all LEDs + + If :meth:`blink` is currently active, it will be stopped first. + + :param int args: + The index(es) of the LED(s) to turn off. If no indexes are + specified turn off all LEDs. + """ + self._stop_blink() + if args: + for index in args: + self[index].off() + else: + super().off() + + def toggle(self, *args): + """ + If no arguments are specified, toggle the state of all LEDs. If + arguments are specified, they must be the indexes of the LEDs you wish + to toggle. For example:: + + from gpiozero import LEDBoard + + leds = LEDBoard(2, 3, 4, 5) + leds.toggle(0) # turn on the first LED (pin 2) + leds.toggle(-1) # turn on the last LED (pin 5) + leds.toggle() # turn the first and last LED off, and the + # middle pair on + + If :meth:`blink` is currently active, it will be stopped first. + + :param int args: + The index(es) of the LED(s) to toggle. If no indexes are specified + toggle the state of all LEDs. + """ + self._stop_blink() + if args: + for index in args: + self[index].toggle() + else: + super().toggle() + + def blink( + self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, + n=None, background=True): + """ + Make all the LEDs turn on and off repeatedly. + + :param float on_time: + Number of seconds on. Defaults to 1 second. + + :param float off_time: + Number of seconds off. Defaults to 1 second. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 0. Must be 0 if + ``pwm`` was :data:`False` when the class was constructed + (:exc:`ValueError` will be raised if not). + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 0. Must be 0 if + ``pwm`` was :data:`False` when the class was constructed + (:exc:`ValueError` will be raised if not). + + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True`, start a background thread to continue blinking and + return immediately. If :data:`False`, only return when the blink is + finished (warning: the default value of *n* will result in this + method never returning). + """ + for led in self.leds: + if isinstance(led, LED): + if fade_in_time: + raise ValueError('fade_in_time must be 0 with non-PWM LEDs') + if fade_out_time: + raise ValueError('fade_out_time must be 0 with non-PWM LEDs') + self._stop_blink() + self._blink_thread = GPIOThread( + self._blink_device, + (on_time, off_time, fade_in_time, fade_out_time, n)) + self._blink_thread.start() + if not background: + self._blink_thread.join() + self._blink_thread = None + + def _stop_blink(self, led=None): + if led is None: + if self._blink_thread: + self._blink_thread.stop() + self._blink_thread = None + else: + with self._blink_lock: + self._blink_leds.remove(led) + + def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True): + """ + Make all LEDs fade in and out repeatedly. Note that this method will + only work if the *pwm* parameter was :data:`True` at construction time. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 1. + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 1. + + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True` (the default), start a background thread to + continue blinking and return immediately. If :data:`False`, only + return when the blink is finished (warning: the default value of + *n* will result in this method never returning). + """ + on_time = off_time = 0 + self.blink( + on_time, off_time, fade_in_time, fade_out_time, n, background) + + def _blink_device( + self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25): + sequence = [] + if fade_in_time > 0: + sequence += [ + (i * (1 / fps) / fade_in_time, 1 / fps) + for i in range(int(fps * fade_in_time)) + ] + sequence.append((1, on_time)) + if fade_out_time > 0: + sequence += [ + (1 - (i * (1 / fps) / fade_out_time), 1 / fps) + for i in range(int(fps * fade_out_time)) + ] + sequence.append((0, off_time)) + if n is None: + sequence = cycle(sequence) + else: + sequence = chain.from_iterable(repeat(sequence, n)) + with self._blink_lock: + self._blink_leds = list(self.leds) + for led in self._blink_leds: + if led._controller not in (None, self): + led._controller._stop_blink(led) + led._controller = self + for value, delay in sequence: + with self._blink_lock: + if not self._blink_leds: + break + for led in self._blink_leds: + led._write(value) + if self._blink_thread.stopping.wait(delay): + break + + +class LEDBarGraph(LEDCollection): + """ + Extends :class:`LEDCollection` to control a line of LEDs representing a + bar graph. Positive values (0 to 1) light the LEDs from first to last. + Negative values (-1 to 0) light the LEDs from last to first. + + The following example demonstrates turning on the first two and last two + LEDs in a board containing five LEDs attached to GPIOs 2 through 6:: + + from gpiozero import LEDBarGraph + from time import sleep + + graph = LEDBarGraph(2, 3, 4, 5, 6) + graph.value = 2/5 # Light the first two LEDs only + sleep(1) + graph.value = -2/5 # Light the last two LEDs only + sleep(1) + graph.off() + + As with all other output devices, :attr:`source` and :attr:`values` are + supported:: + + from gpiozero import LEDBarGraph, MCP3008 + from signal import pause + + graph = LEDBarGraph(2, 3, 4, 5, 6, pwm=True) + pot = MCP3008(channel=0) + + graph.source = pot + + pause() + + :type pins: int or str + :param \\*pins: + Specify the GPIO pins that the LEDs of the bar graph are attached to. + See :ref:`pin-numbering` for valid pin numbers. You can designate as + many pins as necessary. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances for each pin. If + :data:`False` (the default), construct regular :class:`LED` instances. + This parameter can only be specified as a keyword parameter. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set all the + associated pins to HIGH. If :data:`False`, the :meth:`on` method will + set all pins to LOW (the :meth:`off` method always does the opposite). + This parameter can only be specified as a keyword parameter. + + :param float initial_value: + The initial :attr:`value` of the graph given as a float between -1 and + +1. Defaults to 0.0. This parameter can only be specified as a + keyword parameter. + + :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, *pins, pwm=False, active_high=True, initial_value=0.0, + pin_factory=None): + # Don't allow graphs to contain collections + for pin in pins: + if isinstance(pin, Device): + raise CompositeDeviceBadDevice( + 'Only pins may be specified for LEDBarGraph') + super().__init__( + *pins, pwm=pwm, active_high=active_high, pin_factory=pin_factory) + try: + self.value = initial_value + except: + self.close() + raise + + @property + def value(self): + """ + The value of the LED bar graph. When no LEDs are lit, the value is 0. + When all LEDs are lit, the value is 1. Values between 0 and 1 + light LEDs linearly from first to last. Values between 0 and -1 + light LEDs linearly from last to first. + + To light a particular number of LEDs, simply divide that number by + the number of LEDs. For example, if your graph contains 3 LEDs, the + following will light the first:: + + from gpiozero import LEDBarGraph + + graph = LEDBarGraph(12, 16, 19) + graph.value = 1/3 + + .. note:: + + Setting value to -1 will light all LEDs. However, querying it + subsequently will return 1 as both representations are the same in + hardware. The readable range of :attr:`value` is effectively + -1 < value <= 1. + """ + result = sum(led.value for led in self) + if self[0].value < self[-1].value: + result = -result + return result / len(self) + + @value.setter + def value(self, value): + if not -1 <= value <= 1: + raise OutputDeviceBadValue( + 'LEDBarGraph value must be between -1 and 1') + count = len(self) + leds = self + if value < 0: + leds = reversed(leds) + value = -value + if isinstance(self[0], PWMLED): + calc_value = lambda index: min(1, max(0, count * value - index)) + else: + calc_value = lambda index: value >= ((index + 1) / count) + for index, led in enumerate(leds): + led.value = calc_value(index) + + @property + def lit_count(self): + """ + The number of LEDs on the bar graph actually lit up. Note that just + like :attr:`value`, this can be negative if the LEDs are lit from last + to first. + """ + lit_value = self.value * len(self) + if not isinstance(self[0], PWMLED): + lit_value = int(lit_value) + return lit_value + + @lit_count.setter + def lit_count(self, value): + self.value = value / len(self) + + +class LEDCharFont(MutableMapping): + """ + Contains a mapping of values to tuples of LED states. + + This effectively acts as a "font" for :class:`LEDCharDisplay`, and two + default fonts (for 7-segment and 14-segment displays) are shipped with GPIO + Zero by default. You can construct your own font instance from a + :class:`dict` which maps values (usually single-character strings) to + a tuple of LED states:: + + from gpiozero import LEDCharDisplay, LEDCharFont + + my_font = LEDCharFont({ + ' ': (0, 0, 0, 0, 0, 0, 0), + 'D': (1, 1, 1, 1, 1, 1, 0), + 'A': (1, 1, 1, 0, 1, 1, 1), + 'd': (0, 1, 1, 1, 1, 0, 1), + 'a': (1, 1, 1, 1, 1, 0, 1), + }) + display = LEDCharDisplay(26, 13, 12, 22, 17, 19, 6, dp=5, font=my_font) + display.value = 'D' + + Font instances are mutable and can be changed while actively in use by + an instance of :class:`LEDCharDisplay`. However, changing the font will + *not* change the state of the LEDs in the display (though it may change + the :attr:`~LEDCharDisplay.value` of the display when next queried). + + .. note:: + + Your custom mapping should always include a value (typically space) + which represents all the LEDs off. This will usually be the default + value for an instance of :class:`LEDCharDisplay`. + + You may also wish to load fonts from a friendly text-based format. A simple + parser for such formats (supporting an arbitrary number of segments) is + provided by :func:`gpiozero.fonts.load_segment_font`. + """ + def __init__(self, font): + super().__init__() + self._map = { + char: tuple(int(bool(pin)) for pin in pins) + for char, pins in font.items() + } + self._refresh_rmap() + + def __repr__(self): + return f'{self.__class__.__name__}({pformat(self._map, indent=4)})' + + def _refresh_rmap(self): + # The reverse mapping is pre-calculated for speed of lookup. Given that + # the font mapping can be 1:n, we cannot guarantee the reverse is + # unique. In case the provided font is an ordered dictionary, we + # explicitly take only the first definition of each non-unique pin + # definition so that value lookups are predictable + rmap = {} + for char, pins in self._map.items(): + rmap.setdefault(pins, char) + self._rmap = rmap + + def __len__(self): + return len(self._map) + + def __iter__(self): + return iter(self._map) + + def __getitem__(self, char): + return self._map[char] + + def __setitem__(self, char, pins): + try: + # This is necessary to ensure that _rmap is correct in the case + # that we're overwriting an existing char->pins mapping + del self[char] + except KeyError: + pass + pins = tuple(int(bool(pin)) for pin in pins) + self._map[char] = pins + self._rmap.setdefault(pins, char) + + def __delitem__(self, char): + pins = self._map[char] + del self._map[char] + # If the reverse mapping of the char's pins maps to the char we need + # to find if it now maps to another char (given the n:1 mapping) + if self._rmap[pins] == char: + del self._rmap[pins] + for char, char_pins in self._map.items(): + if pins == char_pins: + self._rmap[pins] = char + break + + +class LEDCharDisplay(LEDCollection): + """ + Extends :class:`LEDCollection` for a multi-segment LED display. + + `Multi-segment LED displays`_ typically have 7 pins (labelled "a" through + "g") representing 7 LEDs layed out in a figure-of-8 fashion. Frequently, an + eigth pin labelled "dp" is included for a trailing decimal-point: + + .. code-block:: text + + a + ━━━━━ + f ┃ ┃ b + ┃ g ┃ + ━━━━━ + e ┃ ┃ c + ┃ ┃ + ━━━━━ • dp + d + + Other common layouts are 9, 14, and 16 segment displays which include + additional segments permitting more accurate renditions of alphanumerics. + For example: + + .. code-block:: text + + a + ━━━━━ + f ┃╲i┃j╱┃ b + ┃ ╲┃╱k┃ + g━━ ━━h + e ┃ ╱┃╲n┃ c + ┃╱l┃m╲┃ + ━━━━━ • dp + d + + Such displays have either a common anode, or common cathode pin. This class + defaults to the latter; when using a common anode display *active_high* + should be set to :data:`False`. + + Instances of this class can be used to display characters or control + individual LEDs on the display. For example:: + + from gpiozero import LEDCharDisplay + + char = LEDCharDisplay(4, 5, 6, 7, 8, 9, 10, active_high=False) + char.value = 'C' + + If the class is constructed with 7 or 14 segments, a default :attr:`font` + will be loaded, mapping some ASCII characters to typical layouts. In other + cases, the default mapping will simply assign " " (space) to all LEDs off. + You can assign your own mapping at construction time or after + instantiation. + + While the example above shows the display with a :class:`str` value, + theoretically the *font* can map any value that can be the key in a + :class:`dict`, so the value of the display can be likewise be any valid + key value (e.g. you could map integer digits to LED patterns). That said, + there is one exception to this: when *dp* is specified to enable the + decimal-point, the :attr:`value` must be a :class:`str` as the presence + or absence of a "." suffix indicates whether the *dp* LED is lit. + + :type pins: int or str + :param \\*pins: + Specify the GPIO pins that the multi-segment display is attached to. + Pins should be in the LED segment order A, B, C, D, E, F, G, and will + be named automatically by the class. If a decimal-point pin is + present, specify it separately as the *dp* parameter. + + :type dp: int or str + :param dp: + If a decimal-point segment is present, specify it as this named + parameter. + + :type font: dict or None + :param font: + A mapping of values (typically characters, but may also be numbers) to + tuples of LED states. A default mapping for ASCII characters is + provided for 7 and 14 segment displays. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances for each pin. If + :data:`False` (the default), construct regular :class:`LED` instances. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set all the + associated pins to HIGH. If :data:`False`, the :meth:`on` method will + set all pins to LOW (the :meth:`off` method always does the opposite). + + :param initial_value: + The initial value to display. Defaults to space (" ") which typically + maps to all LEDs being inactive. If :data:`None`, each device will be + left in whatever state the pin is found in when configured for output + (warning: this can be on). + + :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). + + .. _Multi-segment LED displays: https://en.wikipedia.org/wiki/Seven-segment_display + """ + def __init__(self, *pins, dp=None, font=None, pwm=False, active_high=True, + initial_value=" ", pin_factory=None): + if not 1 < len(pins) <= 26: + raise PinInvalidPin( + 'Must have between 2 and 26 LEDs in LEDCharDisplay') + for pin in pins: + if isinstance(pin, LEDCollection): + raise PinInvalidPin( + 'Cannot use LEDCollection in LEDCharDisplay') + + if font is None: + if len(pins) == 7: + with resources.files('gpiozero.fonts').joinpath('7seg.txt').open() as f: + font = load_font_7seg(f) + elif len(pins) == 14: + with resources.files('gpiozero.fonts').joinpath('14seg.txt').open() as f: + font = load_font_14seg(f) + else: + # Construct a default dict containing a definition for " " + font = {" ": (0,) * len(pins)} + self._font = LEDCharFont(font) + + pins = {chr(ord('a') + i): pin for i, pin in enumerate(pins)} + order = sorted(pins.keys()) + if dp is not None: + pins['dp'] = dp + order.append('dp') + super().__init__( + pwm=pwm, active_high=active_high, initial_value=None, + _order=order, pin_factory=pin_factory, **pins) + if initial_value is not None: + self.value = initial_value + + @property + def font(self): + """ + An :class:`LEDCharFont` mapping characters to tuples of LED states. + The font is mutable after construction. You can assign a tuple of LED + states to a character to modify the font, delete an existing character + in the font, or assign a mapping of characters to tuples to replace the + entire font. + + Note that modifying the :attr:`font` never alters the underlying LED + states. Only assignment to :attr:`value`, or calling the inherited + :class:`LEDCollection` methods (:meth:`on`, :meth:`off`, etc.) modifies + LED states. However, modifying the font may alter the character + returned by querying :attr:`value`. + """ + return self._font + + @font.setter + def font(self, value): + self._font = LEDCharFont(value) + + @property + def value(self): + """ + The character the display should show. This is mapped by the current + :attr:`font` to a tuple of LED states which is applied to the + underlying LED objects when this attribute is set. + + When queried, the current LED states are looked up in the font to + determine the character shown. If the current LED states do not + correspond to any character in the :attr:`font`, the value is + :data:`None`. + + It is possible for multiple characters in the font to map to the same + LED states (e.g. S and 5). In this case, if the font was constructed + from an ordered mapping (which is the default), then the first matching + mapping will always be returned. This also implies that the value + queried need not match the value set. + """ + state = super().value + if hasattr(self, 'dp'): + state, dp = state[:-1], state[-1] + else: + dp = False + try: + result = self._font._rmap[state] + except KeyError: + # Raising exceptions on lookup is problematic; in case the LED + # state is not representable we simply return None (although + # technically that is a valid item we can map :) + return None + else: + if dp: + return result + '.' + else: + return result + + @value.setter + def value(self, value): + for led, v in zip(self, self._parse_state(value)): + led.value = v + + def _parse_state(self, value): + if hasattr(self, 'dp'): + if len(value) > 1 and value.endswith('.'): + value = value[:-1] + dp = 1 + else: + dp = 0 + return self._font[value] + (dp,) + else: + return self._font[value] + + +class LEDMultiCharDisplay(CompositeOutputDevice): + """ + Wraps :class:`LEDCharDisplay` for multi-character `multiplexed`_ LED + character displays. + + The class is constructed with a *char* which is an instance of the + :class:`LEDCharDisplay` class, capable of controlling the LEDs in one + character of the display, and an additional set of *pins* that represent + the common cathode (or anode) of each character. + + .. warning:: + + You should not attempt to connect the common cathode (or anode) off + each character directly to a GPIO. Rather, use a set of transistors (or + some other suitable component capable of handling the current of all + the segment LEDs simultaneously) to connect the common cathode to + ground (or the common anode to the supply) and control those + transistors from the GPIOs specified under *pins*. + + The *active_high* parameter defaults to :data:`True`. Note that it only + applies to the specified *pins*, which are assumed to be controlling a set + of transistors (hence the default). The specified *char* will use its own + *active_high* parameter. Finally, *initial_value* defaults to a tuple of + :attr:`~LEDCharDisplay.value` attribute of the specified display multiplied + by the number of *pins* provided. + + When the :attr:`value` is set such that one or more characters in the + display differ in value, a background thread is implicitly started to + rotate the active character, relying on `persistence of vision`_ to display + the complete value. + + .. _multiplexed: https://en.wikipedia.org/wiki/Multiplexed_display + .. _persistence of vision: https://en.wikipedia.org/wiki/Persistence_of_vision + """ + def __init__(self, char, *pins, active_high=True, initial_value=None, + pin_factory=None): + if not isinstance(char, LEDCharDisplay): + raise ValueError('char must be an LEDCharDisplay') + if initial_value is None: + initial_value = (char.value,) * len(pins) + if pin_factory is None: + pin_factory = char.pin_factory + self._plex_thread = None + self._plex_delay = 0.005 + plex = CompositeOutputDevice(*( + OutputDevice( + pin, active_high=active_high, initial_value=None, + pin_factory=pin_factory) + for pin in pins + )) + super().__init__( + plex=plex, char=char, pin_factory=pin_factory) + self.value = initial_value + + def close(self): + try: + self._stop_plex() + except AttributeError: + pass + super().close() + + def _stop_plex(self): + if self._plex_thread: + self._plex_thread.stop() + self._plex_thread = None + + @property + def plex_delay(self): + """ + The delay (measured in seconds) in the loop used to switch each + character in the multiplexed display on. Defaults to 0.005 seconds + which is generally sufficient to provide a "stable" (non-flickery) + display. + """ + return self._plex_delay + + @plex_delay.setter + def plex_delay(self, value): + if value < 0: + raise BadWaitTime('plex_delay must be 0 or greater') + self._plex_delay = float(value) + + @property + def value(self): + """ + The sequence of values to display. + + This can be any sequence containing keys from the + :attr:`~LEDCharDisplay.font` of the associated character display. For + example, if the value consists only of single-character strings, it's + valid to assign a string to this property (as a string is simply a + sequence of individual character keys):: + + from gpiozero import LEDCharDisplay, LEDMultiCharDisplay + + c = LEDCharDisplay(4, 5, 6, 7, 8, 9, 10) + d = LEDMultiCharDisplay(c, 19, 20, 21, 22) + d.value = 'LEDS' + + However, things get more complicated if a decimal point is in use as + then this class needs to know explicitly where to break the value for + use on each character of the display. This can be handled by simply + assigning a sequence of strings thus:: + + from gpiozero import LEDCharDisplay, LEDMultiCharDisplay + + c = LEDCharDisplay(4, 5, 6, 7, 8, 9, 10) + d = LEDMultiCharDisplay(c, 19, 20, 21, 22) + d.value = ('L.', 'E', 'D', 'S') + + This is how the value will always be represented when queried (as a + tuple of individual values) as it neatly handles dealing with + heterogeneous types and the aforementioned decimal point issue. + + .. note:: + + The value also controls whether a background thread is in use to + multiplex the display. When all positions in the value are equal + the background thread is disabled and all characters are + simultaneously enabled. + """ + return self._value + + @value.setter + def value(self, value): + if len(value) > len(self.plex): + raise ValueError( + 'length of value must not exceed the number of characters in ' + 'the display') + elif len(value) < len(self.plex): + # Right-align the short value on the display + value = (' ',) * (len(self.plex) - len(value)) + tuple(value) + else: + value = tuple(value) + + # Get the list of tuples of states that the character LEDs will pass + # through. Prune any entirely blank state (which we can skip by never + # activating the plex for them) but remember which plex index each + # (non-blank) state is associated with + states = {} + for index, char in enumerate(value): + state = self.char._parse_state(char) + if any(state): + states.setdefault(state, set()).add(index) + # Calculate the transitions between states for an ordering of chars + # based on activated LEDs. This a vague attempt at minimizing the + # number of LEDs that need flipping between chars; to do this + # "properly" is O(n!) which gets silly quickly so ... fudge it + order = sorted(states) + if len(order) > 1: + transitions = [ + [(self.plex[index], 0) for index in states[old]] + + [ + (led, new_value) + for led, old_value, new_value in zip(self.char, old, new) + if old_value ^ new_value + ] + + [(self.plex[index], 1) for index in states[new]] + for old, new in pairwise(order + [order[0]]) + ] + else: + transitions = [] + + # Stop any current display thread and disable the display + self._stop_plex() + self.plex.off() + + # If there's any characters to display, set the character LEDs to the + # state of the first character in the display order. If there's + # transitions to display, activate the plex thread; otherwise, just + # switch on each plex with a char to display + if order: + for led, state in zip(self.char, order[0]): + led.value = state + if transitions: + self._plex_thread = GPIOThread(self._show_chars, (transitions,)) + self._plex_thread.start() + else: + for index in states[order[0]]: + self.plex[index].on() + self._value = value + + def _show_chars(self, transitions): + for transition in cycle(transitions): + for device, value in transition: + device.value = value + if self._plex_thread.stopping.wait(self._plex_delay): + break + + +class PiHutXmasTree(LEDBoard): + """ + Extends :class:`LEDBoard` for `The Pi Hut's Xmas board`_: a 3D Christmas + tree board with 24 red LEDs and a white LED as a star on top. + + The 24 red LEDs can be accessed through the attributes led0, led1, led2, + and so on. The white star LED is accessed through the :attr:`star` + attribute. Alternatively, as with all descendents of :class:`LEDBoard`, + you can treat the instance as a sequence of LEDs (the first element is the + :attr:`star`). + + The Xmas Tree board pins are fixed and therefore there's no need to specify + them when constructing this class. The following example turns all the LEDs + on one at a time:: + + from gpiozero import PiHutXmasTree + from time import sleep + + tree = PiHutXmasTree() + + for light in tree: + light.on() + sleep(1) + + The following example turns the star LED on and sets all the red LEDs to + flicker randomly:: + + from gpiozero import PiHutXmasTree + from gpiozero.tools import random_values + from signal import pause + + tree = PiHutXmasTree(pwm=True) + + tree.star.on() + + for led in tree[1:]: + led.source_delay = 0.1 + led.source = random_values() + + pause() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances for each pin. If + :data:`False` (the default), construct regular :class:`LED` instances. + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _The Pi Hut's Xmas board: https://thepihut.com/xmas + + .. attribute:: star + + Returns the :class:`LED` or :class:`PWMLED` representing the white + star on top of the tree. + + .. attribute:: led0, led1, led2, ... + + Returns the :class:`LED` or :class:`PWMLED` representing one of the red + LEDs. There are actually 24 of these properties named led0, led1, and + so on but for the sake of brevity we represent all 24 under this + section. + """ + def __init__(self, *, pwm=False, initial_value=False, pin_factory=None): + pins_dict = OrderedDict(star=2) + pins = (4, 15, 13, 21, 25, 8, 5, 10, 16, 17, 27, 26, + 24, 9, 12, 6, 20, 19, 14, 18, 11, 7, 23, 22) + for i, pin in enumerate(pins): + pins_dict[f'led{i + 1:d}'] = pin + super().__init__( + pwm=pwm, initial_value=initial_value, + _order=pins_dict.keys(), + pin_factory=pin_factory, + **pins_dict + ) + + +class LedBorg(RGBLED): + """ + Extends :class:`RGBLED` for the `PiBorg LedBorg`_: an add-on board + containing a very bright RGB LED. + + The LedBorg pins are fixed and therefore there's no need to specify them + when constructing this class. The following example turns the LedBorg + purple:: + + from gpiozero import LedBorg + + led = LedBorg() + led.color = (1, 0, 1) + + :type initial_value: ~colorzero.Color or tuple + :param initial_value: + The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``. + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMLED` instances for + each component of the LedBorg. If :data:`False`, construct regular + :class:`LED` instances, which prevents smooth color graduations. + + :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). + + .. _PiBorg LedBorg: https://www.piborg.org/ledborg + """ + + def __init__(self, *, initial_value=(0, 0, 0), pwm=True, pin_factory=None): + super().__init__( + red='BOARD11', green='BOARD13', blue='BOARD15', + pwm=pwm, initial_value=initial_value, pin_factory=pin_factory + ) + + +class PiLiter(LEDBoard): + """ + Extends :class:`LEDBoard` for the `Ciseco Pi-LITEr`_: a strip of 8 very + bright LEDs. + + The Pi-LITEr pins are fixed and therefore there's no need to specify them + when constructing this class. The following example turns on all the LEDs + of the Pi-LITEr:: + + from gpiozero import PiLiter + + lite = PiLiter() + lite.on() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances for each pin. If + :data:`False` (the default), construct regular :class:`LED` instances. + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each LED will be left in whatever state the pin is found + in when configured for output (warning: this can be on). If + :data:`True`, the each LED will be switched on initially. + + :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). + + .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ + """ + + def __init__(self, *, pwm=False, initial_value=False, pin_factory=None): + pins = ('BOARD7', 'BOARD11', 'BOARD13', 'BOARD12', + 'BOARD15', 'BOARD16', 'BOARD18', 'BOARD22') + super().__init__( + *pins, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory + ) + + +class PiLiterBarGraph(LEDBarGraph): + """ + Extends :class:`LEDBarGraph` to treat the `Ciseco Pi-LITEr`_ as an + 8-segment bar graph. + + The Pi-LITEr pins are fixed and therefore there's no need to specify them + when constructing this class. The following example sets the graph value + to 0.5:: + + from gpiozero import PiLiterBarGraph + + graph = PiLiterBarGraph() + graph.value = 0.5 + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances for each pin. If + :data:`False` (the default), construct regular :class:`LED` instances. + + :param float initial_value: + The initial :attr:`value` of the graph given as a float between -1 and + +1. Defaults to ``0.0``. + + :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). + + .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ + """ + + def __init__(self, *, pwm=False, initial_value=0.0, pin_factory=None): + pins = ('BOARD7', 'BOARD11', 'BOARD13', 'BOARD12', + 'BOARD15', 'BOARD16', 'BOARD18', 'BOARD22') + super().__init__( + *pins, pwm=pwm, initial_value=initial_value, pin_factory=pin_factory + ) + + +class TrafficLights(LEDBoard): + """ + Extends :class:`LEDBoard` for devices containing red, yellow, and green + LEDs. + + The following example initializes a device connected to GPIO pins 2, 3, + and 4, then lights the amber (yellow) LED attached to GPIO 3:: + + from gpiozero import TrafficLights + + traffic = TrafficLights(2, 3, 4) + traffic.amber.on() + + :type red: int or str + :param red: + The GPIO pin that the red LED is attached to. See :ref:`pin-numbering` + for valid pin numbers. + + :type amber: int or str or None + :param amber: + The GPIO pin that the amber LED is attached to. See + :ref:`pin-numbering` for valid pin numbers. + + :type yellow: int or str or None + :param yellow: + The GPIO pin that the yellow LED is attached to. This is merely an + alias for the ``amber`` parameter; you can't specify both ``amber`` and + ``yellow``. See :ref:`pin-numbering` for valid pin numbers. + + :type green: int or str + :param green: + The GPIO pin that the green LED is attached to. See + :ref:`pin-numbering` for valid pin numbers. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. attribute:: red + + The red :class:`LED` or :class:`PWMLED`. + + .. attribute:: amber + + The amber :class:`LED` or :class:`PWMLED`. Note that this attribute + will not be present when the instance is constructed with the + *yellow* keyword parameter. + + .. attribute:: yellow + + The yellow :class:`LED` or :class:`PWMLED`. Note that this attribute + will only be present when the instance is constructed with the + *yellow* keyword parameter. + + .. attribute:: green + + The green :class:`LED` or :class:`PWMLED`. + """ + def __init__(self, red=None, amber=None, green=None, *, + pwm=False, initial_value=False, yellow=None, + pin_factory=None): + if amber is not None and yellow is not None: + raise OutputDeviceBadValue( + 'Only one of amber or yellow can be specified') + devices = OrderedDict((('red', red), )) + self._display_yellow = amber is None and yellow is not None + if self._display_yellow: + devices['yellow'] = yellow + else: + devices['amber'] = amber + devices['green'] = green + if not all(p is not None for p in devices.values()): + raise GPIOPinMissing( + f'{", ".join(devices.keys())} pins must be provided') + super().__init__( + pwm=pwm, initial_value=initial_value, + _order=devices.keys(), pin_factory=pin_factory, + **devices) + def __getattr__(self, name): + if name == 'amber' and self._display_yellow: + name = 'yellow' + elif name == 'yellow' and not self._display_yellow: + name = 'amber' + return super().__getattr__(name) -class TrafficLights(object): - def __init__(self, red=None, amber=None, green=None): - if not all([red, amber, green]): - raise GPIODeviceError('Red, Amber and Green pins must be provided') - self.red = LED(red) - self.amber = LED(amber) - self.green = LED(green) - self.lights = [self.red, self.amber, self.green] +class PiTraffic(TrafficLights): + """ + Extends :class:`TrafficLights` for the `Low Voltage Labs PI-TRAFFIC`_ + vertical traffic lights board when attached to GPIO pins 9, 10, and 11. - def lights_on(self): - for led in self.lights: - led.on() + There's no need to specify the pins if the PI-TRAFFIC is connected to the + default pins (9, 10, 11). The following example turns on the amber LED on + the PI-TRAFFIC:: - def lights_off(self): - for led in self.lights: - led.off() + from gpiozero import PiTraffic + traffic = PiTraffic() + traffic.amber.on() -class PiTraffic(TrafficLights): - def __init__(self): - self.red = LED(9) - self.amber = LED(10) - self.green = LED(11) - self.lights = [self.red, self.amber, self.green] + To use the PI-TRAFFIC board when attached to a non-standard set of pins, + simply use the parent class, :class:`TrafficLights`. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param bool initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/ + """ + def __init__(self, *, pwm=False, initial_value=False, pin_factory=None): + super().__init__( + 'BOARD21', 'BOARD19', 'BOARD23', + pwm=pwm, initial_value=initial_value, pin_factory=pin_factory + ) + + +class PiStop(TrafficLights): + """ + Extends :class:`TrafficLights` for the `PiHardware Pi-Stop`_: a vertical + traffic lights board. + + The following example turns on the amber LED on a Pi-Stop connected to + location ``A+``:: + + from gpiozero import PiStop + + traffic = PiStop('A+') + traffic.amber.on() + + :param str location: + The `location`_ on the GPIO header to which the Pi-Stop is connected. + Must be one of: ``A``, ``A+``, ``B``, ``B+``, ``C``, ``D``. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param bool initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _PiHardware Pi-Stop: https://pihw.wordpress.com/meltwaters-pi-hardware-kits/pi-stop/ + .. _location: https://github.com/PiHw/Pi-Stop/blob/master/markdown_source/markdown/Discover-PiStop.md + """ + LOCATIONS = { + 'A': ('BOARD26', 'BOARD24', 'BOARD22'), + 'A+': ('BOARD40', 'BOARD38', 'BOARD36'), + 'B': ('BOARD19', 'BOARD21', 'BOARD23'), + 'B+': ('BOARD33', 'BOARD35', 'BOARD37'), + 'C': ('BOARD12', 'BOARD10', 'BOARD8'), + 'D': ('BOARD3', 'BOARD5', 'BOARD7'), + } + + def __init__( + self, location=None, *, pwm=False, initial_value=False, + pin_factory=None): + gpios = self.LOCATIONS.get(location, None) + if gpios is None: + locations = ', '.join(sorted(self.LOCATIONS.keys())) + raise ValueError(f'location must be one of: {locations}') + super().__init__( + *gpios, pwm=pwm, initial_value=initial_value, + pin_factory=pin_factory) + + +class StatusZero(LEDBoard): + """ + Extends :class:`LEDBoard` for The Pi Hut's `STATUS Zero`_: a Pi Zero sized + add-on board with three sets of red/green LEDs to provide a status + indicator. + + The following example designates the first strip the label "wifi" and the + second "raining", and turns them green and red respectfully:: + + from gpiozero import StatusZero + + status = StatusZero('wifi', 'raining') + status.wifi.green.on() + status.raining.red.on() + + Each designated label will contain two :class:`LED` objects named "red" + and "green". + + :param str \\*labels: + Specify the names of the labels you wish to designate the strips to. + You can list up to three labels. If no labels are given, three strips + will be initialised with names 'one', 'two', and 'three'. If some, but + not all strips are given labels, any remaining strips will not be + initialised. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param bool initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _STATUS Zero: https://thepihut.com/statuszero + + .. attribute:: your-label-here, your-label-here, ... + + This entry represents one of the three labelled attributes supported on + the STATUS Zero board. It is an :class:`LEDBoard` which contains: + + .. attribute:: red + + The :class:`LED` or :class:`PWMLED` representing the red LED + next to the label. + + .. attribute:: green + + The :class:`LED` or :class:`PWMLED` representing the green LED + next to the label. + """ + default_labels = ('one', 'two', 'three') + + def __init__(self, *labels, pwm=False, initial_value=False, + pin_factory=None): + pins = ( + ('BOARD11', 'BOARD7'), + ('BOARD15', 'BOARD13'), + ('BOARD21', 'BOARD19'), + ) + if len(labels) == 0: + labels = self.default_labels + elif len(labels) > len(pins): + raise ValueError( + f"StatusZero doesn't support more than {len(pins)} labels") + dup, count = Counter(labels).most_common(1)[0] + if count > 1: + raise ValueError(f"Duplicate label {dup}") + super().__init__( + _order=labels, pin_factory=pin_factory, **{ + label: LEDBoard( + red=red, green=green, _order=('red', 'green'), + pwm=pwm, initial_value=initial_value, + pin_factory=pin_factory) + for (green, red), label in zip(pins, labels) + } + ) + + +class StatusBoard(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice` for The Pi Hut's `STATUS`_ board: a + HAT sized add-on board with five sets of red/green LEDs and buttons to + provide a status indicator with additional input. + + The following example designates the first strip the label "wifi" and the + second "raining", turns the wifi green and then activates the button to + toggle its lights when pressed:: + + from gpiozero import StatusBoard + + status = StatusBoard('wifi', 'raining') + status.wifi.lights.green.on() + status.wifi.button.when_pressed = status.wifi.lights.toggle + + Each designated label will contain a "lights" :class:`LEDBoard` containing + two :class:`LED` objects named "red" and "green", and a :class:`Button` + object named "button". + + :param str \\*labels: + Specify the names of the labels you wish to designate the strips to. + You can list up to five labels. If no labels are given, five strips + will be initialised with names 'one' to 'five'. If some, but not all + strips are given labels, any remaining strips will not be initialised. + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param bool initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _STATUS: https://thepihut.com/status + + .. attribute:: your-label-here, your-label-here, ... + + This entry represents one of the five labelled attributes supported on + the STATUS board. It is an :class:`CompositeOutputDevice` which + contains: + + .. attribute:: lights + + A :class:`LEDBoard` representing the lights next to the label. It + contains: + + .. attribute:: red + + The :class:`LED` or :class:`PWMLED` representing the red LED + next to the label. + + .. attribute:: green + + The :class:`LED` or :class:`PWMLED` representing the green LED + next to the label. + + .. attribute:: button + + A :class:`Button` representing the button next to the label. + """ + default_labels = ('one', 'two', 'three', 'four', 'five') + + def __init__(self, *labels, pwm=False, initial_value=False, + pin_factory=None): + pins = ( + ('BOARD11', 'BOARD7', 'BOARD8'), + ('BOARD15', 'BOARD13', 'BOARD35'), + ('BOARD21', 'BOARD19', 'BOARD10'), + ('BOARD29', 'BOARD23', 'BOARD37'), + ('BOARD33', 'BOARD31', 'BOARD12'), + ) + if len(labels) == 0: + labels = self.default_labels + elif len(labels) > len(pins): + raise ValueError("StatusBoard doesn't support more than five labels") + dup, count = Counter(labels).most_common(1)[0] + if count > 1: + raise ValueError(f"Duplicate label {dup}") + super().__init__( + _order=labels, pin_factory=pin_factory, **{ + label: CompositeOutputDevice( + button=Button(button, pin_factory=pin_factory), + lights=LEDBoard( + red=red, green=green, _order=('red', 'green'), + pwm=pwm, initial_value=initial_value, + pin_factory=pin_factory), + _order=('button', 'lights'), pin_factory=pin_factory + ) + for (green, red, button), label in zip(pins, labels) + } + ) + + +class SnowPi(LEDBoard): + """ + Extends :class:`LEDBoard` for the `Ryanteck SnowPi`_ board. + + The SnowPi pins are fixed and therefore there's no need to specify them + when constructing this class. The following example turns on the eyes, sets + the nose pulsing, and the arms blinking:: + + from gpiozero import SnowPi + + snowman = SnowPi(pwm=True) + snowman.eyes.on() + snowman.nose.pulse() + snowman.arms.blink() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param bool initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html + + .. attribute:: arms + + A :class:`LEDBoard` representing the arms of the snow man. It contains + the following attributes: + + .. attribute:: left, right + + Two :class:`LEDBoard` objects representing the left and right arms + of the snow-man. They contain: + + .. attribute:: top, middle, bottom + + The :class:`LED` or :class:`PWMLED` down the snow-man's arms. + + .. attribute:: eyes + + A :class:`LEDBoard` representing the eyes of the snow-man. It contains: + + .. attribute:: left, right + + The :class:`LED` or :class:`PWMLED` for the snow-man's eyes. + + .. attribute:: nose + + The :class:`LED` or :class:`PWMLED` for the snow-man's nose. + """ + def __init__(self, *, pwm=False, initial_value=False, pin_factory=None): + super().__init__( + arms=LEDBoard( + left=LEDBoard( + top='BOARD11', middle='BOARD12', bottom='BOARD15', + pwm=pwm, initial_value=initial_value, + _order=('top', 'middle', 'bottom'), + pin_factory=pin_factory), + right=LEDBoard( + top='BOARD26', middle='BOARD24', bottom='BOARD21', + pwm=pwm, initial_value=initial_value, + _order=('top', 'middle', 'bottom'), + pin_factory=pin_factory), + _order=('left', 'right'), + pin_factory=pin_factory + ), + eyes=LEDBoard( + left='BOARD16', right='BOARD18', + pwm=pwm, initial_value=initial_value, + _order=('left', 'right'), + pin_factory=pin_factory + ), + nose='BOARD22', + pwm=pwm, initial_value=initial_value, + _order=('eyes', 'nose', 'arms'), + pin_factory=pin_factory + ) + + +class TrafficLightsBuzzer(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice` and is a generic class for HATs with + traffic lights, a button and a buzzer. + + :param TrafficLights lights: + An instance of :class:`TrafficLights` representing the traffic lights + of the HAT. + + :param Buzzer buzzer: + An instance of :class:`Buzzer` representing the buzzer on the HAT. + + :param Button button: + An instance of :class:`Button` representing the button on the HAT. + + :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). + + .. attribute:: lights + + The :class:`TrafficLights` instance passed as the *lights* parameter. + + .. attribute:: buzzer + + The :class:`Buzzer` instance passed as the *buzzer* parameter. + + .. attribute:: button + + The :class:`Button` instance passed as the *button* parameter. + """ + def __init__(self, lights, buzzer, button, *, pin_factory=None): + super().__init__( + lights=lights, buzzer=buzzer, button=button, + _order=('lights', 'buzzer', 'button'), + pin_factory=pin_factory + ) + + +class FishDish(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice` for the `Pi Supply FishDish`_: traffic + light LEDs, a button and a buzzer. + + The FishDish pins are fixed and therefore there's no need to specify them + when constructing this class. The following example waits for the button + to be pressed on the FishDish, then turns on all the LEDs:: + + from gpiozero import FishDish + + fish = FishDish() + fish.button.wait_for_press() + fish.lights.on() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :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). + + .. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/ + """ + def __init__(self, *, pwm=False, pin_factory=None): + super().__init__( + lights=TrafficLights( + 'BOARD21', 'BOARD15', 'BOARD7', pwm=pwm, pin_factory=pin_factory + ), + buzzer=Buzzer('BOARD24', pin_factory=pin_factory), + button=Button('BOARD26', pull_up=False, pin_factory=pin_factory), + _order=('lights', 'buzzer', 'button'), + pin_factory=pin_factory + ) + + +class TrafficHat(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice` for the `Pi Supply Traffic HAT`_: a + board with traffic light LEDs, a button and a buzzer. + + The Traffic HAT pins are fixed and therefore there's no need to specify + them when constructing this class. The following example waits for the + button to be pressed on the Traffic HAT, then turns on all the LEDs:: + + from gpiozero import TrafficHat + + hat = TrafficHat() + hat.button.wait_for_press() + hat.lights.on() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :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). + + .. _Pi Supply Traffic HAT: https://uk.pi-supply.com/products/traffic-hat-for-raspberry-pi + """ + def __init__(self, *, pwm=False, pin_factory=None): + super().__init__( + lights=TrafficLights( + 'BOARD18', 'BOARD16', 'BOARD15', + pwm=pwm, pin_factory=pin_factory + ), + buzzer=Buzzer('BOARD29', pin_factory=pin_factory), + button=Button('BOARD22', pin_factory=pin_factory), + _order=('lights', 'buzzer', 'button'), + pin_factory=pin_factory + ) + + +class TrafficpHat(TrafficLights): + """ + Extends :class:`TrafficLights` for the `Pi Supply Traffic pHAT`_: a small + board with traffic light LEDs. + + The Traffic pHAT pins are fixed and therefore there's no need to specify + them when constructing this class. The following example then turns on all + the LEDs:: + + from gpiozero import TrafficpHat + phat = TrafficpHat() + phat.red.on() + phat.blink() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances. + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _Pi Supply Traffic pHAT: http://pisupp.ly/trafficphat + """ + def __init__(self, *, pwm=False, initial_value=False, pin_factory=None): + super().__init__( + red='BOARD22', amber='BOARD18', green='BOARD16', + pwm=pwm, initial_value=initial_value, pin_factory=pin_factory + ) + + +def PhaseEnableRobot(left=None, right=None, pwm=True, pin_factory=None, *args): + """ + Deprecated alias of :class:`Robot`. The :class:`Robot` class can now be + constructed directly with :class:`Motor` or :class:`PhaseEnableMotor` + classes. + """ + warnings.warn( + DeprecationWarning( + "PhaseEnableRobot is deprecated; please construct Robot directly " + "with PhaseEnableMotor instances")) + return Robot( + PhaseEnableMotor(*left), + PhaseEnableMotor(*right), + pwm=pwm, pin_factory=pin_factory, + *args) + + +class Robot(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` to represent a generic dual-motor robot. + + This class is constructed with two motor instances representing the left + and right wheels of the robot respectively. For example, if the left + motor's controller is connected to GPIOs 4 and 14, while the right motor's + controller is connected to GPIOs 17 and 18 then the following example will + drive the robot forward:: + + from gpiozero import Robot + + robot = Robot(left=Motor(4, 14), right=Motor(17, 18)) + robot.forward() + + :type left: Motor or PhaseEnableMotor + :param left: + A :class:`~gpiozero.Motor` or a :class:`~gpiozero.PhaseEnableMotor` + for the left wheel of the robot. + + :type right: Motor or PhaseEnableMotor + :param right: + A :class:`~gpiozero.Motor` or a :class:`~gpiozero.PhaseEnableMotor` + for the right wheel of the robot. + + :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). + + .. attribute:: left_motor + + The :class:`Motor` on the left of the robot. + + .. attribute:: right_motor + + The :class:`Motor` on the right of the robot. + """ + def __init__(self, left, right, *, pin_factory=None): + if not isinstance(left, (Motor, PhaseEnableMotor)): + if isinstance(left, tuple): + warnings.warn( + DeprecationWarning( + "Passing a tuple as the left parameter of the Robot " + "constructor is deprecated; please pass a Motor or " + "PhaseEnableMotor instance instead")) + left = Motor(*left, pin_factory=pin_factory) + else: + raise GPIOPinMissing('left must be a Motor or PhaseEnableMotor') + if not isinstance(right, (Motor, PhaseEnableMotor)): + if isinstance(right, tuple): + warnings.warn( + DeprecationWarning( + "Passing a tuple as the right parameter of the Robot " + "constructor is deprecated; please pass a Motor or " + "PhaseEnableMotor instance instead")) + right = Motor(*right, pin_factory=pin_factory) + else: + raise GPIOPinMissing('right must be a Motor or PhaseEnableMotor') + super().__init__(left_motor=left, right_motor=right, + _order=('left_motor', 'right_motor'), + pin_factory=pin_factory) + @property + def value(self): + """ + Represents the motion of the robot as a tuple of (left_motor_speed, + right_motor_speed) with ``(-1, -1)`` representing full speed backwards, + ``(1, 1)`` representing full speed forwards, and ``(0, 0)`` + representing stopped. + """ + return super().value -class FishDish(TrafficLights): - def __init__(self): - red, amber, green = (9, 22, 4) - super(FishDish, self).__init__(red, amber, green) - self.buzzer = Buzzer(8) - self.button = Button(7) - self.all = self.lights + [self.buzzer] + @value.setter + def value(self, value): + self.left_motor.value, self.right_motor.value = value + + def forward(self, speed=1, *, curve_left=0, curve_right=0): + """ + Drive the robot forward by running both motors forward. + + :param float speed: + Speed at which to drive the motors, as a value between 0 (stopped) + and 1 (full speed). The default is 1. + + :param float curve_left: + The amount to curve left while moving forwards, by driving the + left motor at a slower speed. Maximum *curve_left* is 1, the + default is 0 (no curve). This parameter can only be specified as a + keyword parameter, and is mutually exclusive with *curve_right*. + + :param float curve_right: + The amount to curve right while moving forwards, by driving the + right motor at a slower speed. Maximum *curve_right* is 1, the + default is 0 (no curve). This parameter can only be specified as a + keyword parameter, and is mutually exclusive with *curve_left*. + """ + if not 0 <= curve_left <= 1: + raise ValueError('curve_left must be between 0 and 1') + if not 0 <= curve_right <= 1: + raise ValueError('curve_right must be between 0 and 1') + if curve_left != 0 and curve_right != 0: + raise ValueError("curve_left and curve_right can't be used at " + "the same time") + self.left_motor.forward(speed * (1 - curve_left)) + self.right_motor.forward(speed * (1 - curve_right)) + + def backward(self, speed=1, *, curve_left=0, curve_right=0): + """ + Drive the robot backward by running both motors backward. + + :param float speed: + Speed at which to drive the motors, as a value between 0 (stopped) + and 1 (full speed). The default is 1. + + :param float curve_left: + The amount to curve left while moving backwards, by driving the + left motor at a slower speed. Maximum *curve_left* is 1, the + default is 0 (no curve). This parameter can only be specified as a + keyword parameter, and is mutually exclusive with *curve_right*. + + :param float curve_right: + The amount to curve right while moving backwards, by driving the + right motor at a slower speed. Maximum *curve_right* is 1, the + default is 0 (no curve). This parameter can only be specified as a + keyword parameter, and is mutually exclusive with *curve_left*. + """ + if not 0 <= curve_left <= 1: + raise ValueError('curve_left must be between 0 and 1') + if not 0 <= curve_right <= 1: + raise ValueError('curve_right must be between 0 and 1') + if curve_left != 0 and curve_right != 0: + raise ValueError("curve_left and curve_right can't be used at " + "the same time") + self.left_motor.backward(speed * (1 - curve_left)) + self.right_motor.backward(speed * (1 - curve_right)) + + def left(self, speed=1): + """ + Make the robot turn left by running the right motor forward and left + motor backward. + + :param float speed: + Speed at which to drive the motors, as a value between 0 (stopped) + and 1 (full speed). The default is 1. + """ + self.right_motor.forward(speed) + self.left_motor.backward(speed) + + def right(self, speed=1): + """ + Make the robot turn right by running the left motor forward and right + motor backward. + + :param float speed: + Speed at which to drive the motors, as a value between 0 (stopped) + and 1 (full speed). The default is 1. + """ + self.left_motor.forward(speed) + self.right_motor.backward(speed) + + def reverse(self): + """ + Reverse the robot's current motor directions. If the robot is currently + running full speed forward, it will run full speed backward. If the + robot is turning left at half-speed, it will turn right at half-speed. + If the robot is currently stopped it will remain stopped. + """ + self.left_motor.reverse() + self.right_motor.reverse() + + def stop(self): + """ + Stop the robot. + """ + self.left_motor.stop() + self.right_motor.stop() + + +class RyanteckRobot(Robot): + """ + Extends :class:`Robot` for the `Ryanteck motor controller board`_. + + The Ryanteck MCB pins are fixed and therefore there's no need to specify + them when constructing this class. The following example drives the robot + forward:: + + from gpiozero import RyanteckRobot + + robot = RyanteckRobot() + robot.forward() + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMOutputDevice` + instances for the motor controller pins, allowing both direction and + variable speed control. If :data:`False`, construct + :class:`DigitalOutputDevice` instances, allowing only direction + control. + + :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). + + .. _Ryanteck motor controller board: https://uk.pi-supply.com/products/ryanteck-rtk-000-001-motor-controller-board-kit-raspberry-pi + """ + + def __init__(self, *, pwm=True, pin_factory=None): + super().__init__( + left=Motor('BOARD11', 'BOARD12', pwm=pwm, pin_factory=pin_factory), + right=Motor('BOARD15', 'BOARD16', pwm=pwm, pin_factory=pin_factory), + pin_factory=pin_factory) + + +class CamJamKitRobot(Robot): + """ + Extends :class:`Robot` for the `CamJam #3 EduKit`_ motor controller board. + + The CamJam robot controller pins are fixed and therefore there's no need + to specify them when constructing this class. The following example drives + the robot forward:: + + from gpiozero import CamJamKitRobot + + robot = CamJamKitRobot() + robot.forward() + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMOutputDevice` + instances for the motor controller pins, allowing both direction and + variable speed control. If :data:`False`, construct + :class:`DigitalOutputDevice` instances, allowing only direction + control. + + :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). + + .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 + """ + def __init__(self, *, pwm=True, pin_factory=None): + super().__init__( + left=Motor('BOARD21', 'BOARD19', pwm=pwm, pin_factory=pin_factory), + right=Motor('BOARD26', 'BOARD24', pwm=pwm, pin_factory=pin_factory), + pin_factory=pin_factory) + + +class PololuDRV8835Robot(Robot): + """ + Extends :class:`Robot` for the `Pololu DRV8835 Dual Motor Driver Kit`_. + + The Pololu DRV8835 pins are fixed and therefore there's no need to specify + them when constructing this class. The following example drives the robot + forward:: + + from gpiozero import PololuDRV8835Robot + + robot = PololuDRV8835Robot() + robot.forward() + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMOutputDevice` + instances for the motor controller's enable pins, allowing both + direction and variable speed control. If :data:`False`, construct + :class:`DigitalOutputDevice` instances, allowing only direction + control. + + :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). + + .. _Pololu DRV8835 Dual Motor Driver Kit: https://www.pololu.com/product/2753 + """ + def __init__(self, *, pwm=True, pin_factory=None): + super().__init__( + left=PhaseEnableMotor('BOARD29', 'BOARD32', pwm=pwm, pin_factory=pin_factory), + right=PhaseEnableMotor('BOARD31', 'BOARD33', pwm=pwm, pin_factory=pin_factory), + pin_factory=pin_factory) + + +class _EnergenieMaster(SharedMixin, CompositeOutputDevice): + def __init__(self, *, pin_factory=None): + self._lock = Lock() + super().__init__( + *( + OutputDevice(pin, pin_factory=pin_factory) + for pin in ('BOARD11', 'BOARD15', 'BOARD16', 'BOARD13') + ), + mode=OutputDevice('BOARD18', pin_factory=pin_factory), + enable=OutputDevice('BOARD22', pin_factory=pin_factory), + _order=('mode', 'enable'), pin_factory=pin_factory + ) + + def close(self): + if getattr(self, '_lock', None): + with self._lock: + super().close() + self._lock = None + + @classmethod + def _shared_key(cls, pin_factory): + # There's only one Energenie master + return None + + def transmit(self, socket, enable): + with self._lock: + try: + code = (8 * bool(enable)) + (8 - socket) + for bit in self[:4]: + bit.value = (code & 1) + code >>= 1 + sleep(0.1) + self.enable.on() + sleep(0.25) + finally: + self.enable.off() + + +class Energenie(SourceMixin, Device): + """ + Extends :class:`Device` to represent an `Energenie socket`_ controller. + + This class is constructed with a socket number and an optional initial + state (defaults to :data:`False`, meaning off). Instances of this class can + be used to switch peripherals on and off. For example:: + + from gpiozero import Energenie + + lamp = Energenie(1) + lamp.on() + + :param int socket: + Which socket this instance should control. This is an integer number + between 1 and 4. + + :type initial_value: bool or None + :param initial_value: + The initial state of the socket. As Energenie sockets provide no + means of reading their state, you may provide an initial state for + the socket, which will be set upon construction. This defaults to + :data:`False` which will switch the socket off. + Specifying :data:`None` will not set any initial state nor transmit any + control signal to the device. + + :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). + + .. _Energenie socket: https://energenie4u.co.uk/index.php/catalogue/product/ENER002-2PI + """ + def __init__(self, socket=None, *, initial_value=False, pin_factory=None): + if socket is None: + raise EnergenieSocketMissing('socket number must be provided') + if not (1 <= socket <= 4): + raise EnergenieBadSocket('socket number must be between 1 and 4') + self._value = None + super().__init__(pin_factory=pin_factory) + self._socket = socket + self._master = _EnergenieMaster(pin_factory=pin_factory) + if initial_value: + self.on() + elif initial_value is not None: + self.off() + + def close(self): + if getattr(self, '_master', None): + self._master.close() + self._master = None + + @property + def closed(self): + return self._master is None + + def __repr__(self): + try: + self._check_open() + return f"" + except DeviceClosed: + return "" + + @property + def socket(self): + """ + Returns the socket number. + """ + return self._socket + + @property + def value(self): + """ + Returns :data:`True` if the socket is on and :data:`False` if the + socket is off. Setting this property changes the state of the socket. + Returns :data:`None` only when constructed with :data:`initial_value` + set to :data:`None` and neither :data:`on()` nor :data:`off()` have + been called since construction. + """ + return self._value + + @value.setter + def value(self, value): + if value is None: + raise TypeError('value cannot be None') + value = bool(value) + self._master.transmit(self._socket, value) + self._value = value + + def on(self): + """ + Turns the socket on. + """ + self.value = True + + def off(self): + """ + Turns the socket off. + """ + self.value = False + + +class PumpkinPi(LEDBoard): + """ + Extends :class:`LEDBoard` for the `ModMyPi PumpkinPi`_ board. + + There are twelve LEDs connected up to individual pins, so for the PumpkinPi + the pins are fixed. For example:: + + from gpiozero import PumpkinPi + + pumpkin = PumpkinPi(pwm=True) + pumpkin.sides.pulse() + pumpkin.off() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED. If :data:`False` (the default), construct regular :class:`LED` + instances + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), all LEDs will be off initially. If + :data:`None`, each device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. + + :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). + + .. _ModMyPi PumpkinPi: https://www.modmypi.com/halloween-pumpkin-programmable-kit + + .. attribute:: sides + + A :class:`LEDBoard` representing the LEDs around the edge of the + pumpkin. It contains: + + .. attribute:: left, right + + Two :class:`LEDBoard` instances representing the LEDs on the left + and right sides of the pumpkin. They each contain: + + .. attribute:: top, midtop, middle, midbottom, bottom + + Each :class:`LED` or :class:`PWMLED` around the specified side + of the pumpkin. + + .. attribute:: eyes + + A :class:`LEDBoard` representing the eyes of the pumpkin. It contains: + + .. attribute:: left, right + + The :class:`LED` or :class:`PWMLED` for each of the pumpkin's eyes. + """ + def __init__(self, *, pwm=False, initial_value=False, pin_factory=None): + super().__init__( + sides=LEDBoard( + left=LEDBoard( + bottom='BOARD12', midbottom='BOARD11', middle='BOARD36', + midtop='BOARD33', top='BOARD18', + pwm=pwm, initial_value=initial_value, + _order=('bottom', 'midbottom', 'middle', 'midtop', 'top'), + pin_factory=pin_factory), + right=LEDBoard( + bottom='BOARD35', midbottom='BOARD38', middle='BOARD40', + midtop='BOARD15', top='BOARD16', + pwm=pwm, initial_value=initial_value, + _order=('bottom', 'midbottom', 'middle', 'midtop', 'top'), + pin_factory=pin_factory), + pwm=pwm, initial_value=initial_value, + _order=('left', 'right'), + pin_factory=pin_factory + ), + eyes=LEDBoard( + left='BOARD32', right='BOARD31', + pwm=pwm, initial_value=initial_value, + _order=('left', 'right'), + pin_factory=pin_factory + ), + pwm=pwm, initial_value=initial_value, + _order=('eyes', 'sides'), + pin_factory=pin_factory + ) + + +class JamHat(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice` for the `ModMyPi JamHat`_ board. + + There are 6 LEDs, two buttons and a tonal buzzer. The pins are fixed. + Usage:: + + from gpiozero import JamHat + + hat = JamHat() + + hat.button_1.wait_for_press() + hat.lights_1.on() + hat.buzzer.play('C4') + hat.button_2.wait_for_press() + hat.off() + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED on the board. If :data:`False` (the default), construct regular + :class:`LED` instances. + + :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). + + .. _ModMyPi JamHat: https://thepihut.com/products/jam-hat + + .. attribute:: lights_1, lights_2 + + Two :class:`LEDBoard` instances representing the top (lights_1) and + bottom (lights_2) rows of LEDs on the JamHat. + + .. attribute:: red, yellow, green + + :class:`LED` or :class:`PWMLED` instances representing the red, + yellow, and green LEDs along the top row. + + .. attribute:: button_1, button_2 + + The left (button_1) and right (button_2) :class:`Button` objects on the + JamHat. + + .. attribute:: buzzer + + The :class:`TonalBuzzer` at the bottom right of the JamHat. + """ + def __init__(self, *, pwm=False, pin_factory=None): + super().__init__( + lights_1=LEDBoard( + red='BOARD29', yellow='BOARD32', green='BOARD36', + pwm=pwm, _order=('red', 'yellow', 'green'), + pin_factory=pin_factory + ), + lights_2=LEDBoard( + red='BOARD31', yellow='BOARD33', green='BOARD11', + pwm=pwm, _order=('red', 'yellow', 'green'), + pin_factory=pin_factory), + button_1=Button('BOARD35', pull_up=False, pin_factory=pin_factory), + button_2=Button('BOARD12', pull_up=False, pin_factory=pin_factory), + buzzer=TonalBuzzer('BOARD38', pin_factory=pin_factory), + _order=('lights_1', 'lights_2', 'button_1', 'button_2', 'buzzer'), + pin_factory=pin_factory + ) def on(self): - for led in self.all: - led.on() + """ + Turns all the LEDs on and makes the buzzer play its mid tone. + """ + self.buzzer.value = 0 + super().on() def off(self): - for led in self.all: - led.off() + """ + Turns all the LEDs off and stops the buzzer. + """ + self.buzzer.value = None + super().off() + + +class Pibrella(CompositeOutputDevice): + """ + Extends :class:`CompositeOutputDevice` for the Cyntech/Pimoroni `Pibrella`_ + board. + + The Pibrella board comprises 3 LEDs, a button, a tonal buzzer, four general + purpose input channels, and four general purpose output channels (with LEDs). + + This class exposes the LEDs, button and buzzer. + + Usage:: + + from gpiozero import Pibrella + + pb = Pibrella() + + pb.button.wait_for_press() + pb.lights.on() + pb.buzzer.play('A4') + pb.off() + + The four input and output channels are exposed so you can create GPIO Zero + devices using these pins without looking up their respective pin numbers:: + + from gpiozero import Pibrella, LED, Button + + pb = Pibrella() + btn = Button(pb.inputs.a, pull_up=False) + led = LED(pb.outputs.e) + + btn.when_pressed = led.on + + :param bool pwm: + If :data:`True`, construct :class:`PWMLED` instances to represent each + LED on the board, otherwise if :data:`False` (the default), construct + regular :class:`LED` instances. + :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). -class PiLiter(object): - def __init__(self): - leds = [4, 17, 27, 18, 22, 23, 24, 25] - self.leds = [LED(led) for led in leds] + .. _Pibrella: http://www.pibrella.com/ + + .. attribute:: lights + + :class:`TrafficLights` instance representing the three LEDs + + .. attribute:: red, amber, green + + :class:`LED` or :class:`PWMLED` instances representing the red, + amber, and green LEDs + + .. attribute:: button + + The red :class:`Button` object on the Pibrella + + .. attribute:: buzzer + + A :class:`TonalBuzzer` object representing the buzzer + + .. attribute:: inputs + + A :func:`~collections.namedtuple` of the input pin numbers + + .. attribute:: a, b, c, d + + .. attribute:: outputs + + A :func:`~collections.namedtuple` of the output pin numbers + + .. attribute:: e, f, g, h + """ + def __init__(self, *, pwm=False, pin_factory=None): + super().__init__( + lights=TrafficLights( + red='BOARD13', amber='BOARD11', green='BOARD7', + pwm=pwm, pin_factory=pin_factory + ), + button=Button('BOARD23', pull_up=False, pin_factory=pin_factory), + buzzer=TonalBuzzer('BOARD12', pin_factory=pin_factory), + _order=('lights', 'button', 'buzzer'), + pin_factory=pin_factory + ) + InputPins = namedtuple('InputPins', ['a', 'b', 'c', 'd']) + OutputPins = namedtuple('OutputPins', ['e', 'f', 'g', 'h']) + self.inputs = InputPins( + a='BOARD21', b='BOARD26', c='BOARD24', d='BOARD19' + ) + self.outputs = OutputPins( + e='BOARD15', f='BOARD16', g='BOARD18', h='BOARD22' + ) def on(self): - for led in self.leds: - led.on() + """ + Turns all the LEDs on and makes the buzzer play its mid tone. + """ + self.buzzer.value = 0 + super().on() def off(self): - for led in self.leds: - led.off() + """ + Turns all the LEDs off and stops the buzzer. + """ + self.buzzer.value = None + super().off() diff --git a/gpiozero/compat.py b/gpiozero/compat.py new file mode 100644 index 000000000..11159c92e --- /dev/null +++ b/gpiozero/compat.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2018 Rick Ansell +# Copyright (c) 2016 Andrew Scheller +# +# 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 diff --git a/gpiozero/devices.py b/gpiozero/devices.py index 22d4048ac..a27936b87 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -1,49 +1,634 @@ -from threading import Thread, Event +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2024 Dave Jones +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2015-2019 Ben Nuttall +# Copyright (c) 2016 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause -from RPi import GPIO +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 -class GPIODeviceError(Exception): - pass +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 -class GPIODevice(object): - def __init__(self, pin=None): +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"" + except DeviceClosed: + return f"" + + 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] + + >>> d[1] + + >>> d[2] + + >>> d.btn + + >>> 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"") + elif named > 0: + return ( + f"") + else: + return ( + f"") + 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 GPIODeviceError('No GPIO pin number given') + 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 = 1 - self._inactive_state = 0 + 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 is_active(self): - return GPIO.input(self.pin) == self._active_state + def value(self): + return self._read() + def __repr__(self): + try: + return ( + f"") + except DeviceClosed: + return f"" -_GPIO_THREADS = set() -def _gpio_threads_shutdown(): - while _GPIO_THREADS: - for t in _GPIO_THREADS.copy(): - t.stop() +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 -class GPIOThread(Thread): - def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): - super(GPIOThread, self).__init__(group, target, name, args, kwargs) - self.stopping = Event() - self.daemon = True - def start(self): - self.stopping.clear() - _GPIO_THREADS.add(self) - super(GPIOThread, self).start() +def _shutdown(): + _threads_shutdown() + _devices_shutdown() - def stop(self): - self.stopping.set() - self.join() - _GPIO_THREADS.discard(self) +atexit.register(_shutdown) diff --git a/gpiozero/exc.py b/gpiozero/exc.py new file mode 100644 index 000000000..c167a36c1 --- /dev/null +++ b/gpiozero/exc.py @@ -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 +# Copyright (c) 2019 Kosovan Sofiia +# Copyright (c) 2019 Ben Nuttall +# Copyright (c) 2016 Andrew Scheller +# +# 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" diff --git a/gpiozero/fonts/14seg.txt b/gpiozero/fonts/14seg.txt new file mode 100644 index 000000000..4098b0ebd --- /dev/null +++ b/gpiozero/fonts/14seg.txt @@ -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 | / +| \| | | | | \| | \ | | | | |/ |/ \| / \ | / +' ' --- --- ' ' --- --- ' ' ' ' ' --- + +&---. $--- (---. )--- [---. ]--- %.... *.... +.... -.... /.... =.... \.... + \ / | | | | | | | / .\|/ . | . . / . .\ + -- --- | | | | . .--- .--- .--- . / .--- . \ +| \ | | | | | | ./ | ./|\ . | . ./ . . \ + --- --- --- --- --- --- . . . . . .--- . + +_.... '.... +. . | +. . +. . +.--- . diff --git a/gpiozero/fonts/7seg.txt b/gpiozero/fonts/7seg.txt new file mode 100644 index 000000000..9b7b18807 --- /dev/null +++ b/gpiozero/fonts/7seg.txt @@ -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. +|_| |_. |.. ._| |_. |_. |.. |_| |.. ..| |.. |.| ._. |_| |_| ._. |_. |_. |.| |_| +|.| |_| |_. |_| |_. |.. |_| |.| |.. ._| |__ |.| |_| |.. ..| |.. ._| |_. |_| ._| diff --git a/gpiozero/fonts/__init__.py b/gpiozero/fonts/__init__.py new file mode 100644 index 000000000..4354fcfe2 --- /dev/null +++ b/gpiozero/fonts/__init__.py @@ -0,0 +1,224 @@ +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2021-2023 Dave Jones +# +# 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, '\\')]) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 0fbec6acf..b632a02e9 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -1,221 +1,1371 @@ -from __future__ import division +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2023 Dave Jones +# Copyright (c) 2015-2021 Ben Nuttall +# Copyright (c) 2020 Robert Erdin +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2020 Dan Jackson +# Copyright (c) 2016-2020 Andrew Scheller +# Copyright (c) 2019 Kosovan Sofiia +# Copyright (c) 2018 Philippe Muller +# Copyright (c) 2016 Steveis +# +# SPDX-License-Identifier: BSD-3-Clause -from time import sleep, time -from threading import Event -from collections import deque +import warnings +from time import sleep +from threading import Event, Lock +from itertools import tee +from statistics import median, mean -from RPi import GPIO -from w1thermsensor import W1ThermSensor +from .exc import ( + InputDeviceError, + DeviceClosed, + DistanceSensorNoEcho, + PinInvalidState, + PWMSoftwareFallback, +) +from .devices import GPIODevice, CompositeDevice +from .mixins import GPIOQueue, EventsMixin, HoldMixin, event +try: + from .pins.pigpio import PiGPIOFactory +except ImportError: + PiGPIOFactory = None -from .devices import GPIODeviceError, GPIODevice, GPIOThread +class InputDevice(GPIODevice): + """ + Represents a generic GPIO input device. -class InputDeviceError(GPIODeviceError): - pass + This class extends :class:`GPIODevice` to add facilities common to GPIO + input devices. The constructor adds the optional *pull_up* parameter to + specify how the pin should be pulled by the internal resistors. The + :attr:`is_active` property is adjusted accordingly so that :data:`True` + still means active regardless of the *pull_up* setting. + :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. -class InputDevice(GPIODevice): - def __init__(self, pin=None, pull_up=True): - super(InputDevice, self).__init__(pin) - self._pull_up = pull_up - self._edge = (GPIO.RISING, GPIO.FALLING)[pull_up] - if pull_up: - self._active_state = 0 - self._inactive_state = 1 - GPIO.setup(pin, GPIO.IN, (GPIO.PUD_DOWN, GPIO.PUD_UP)[pull_up]) + :type pull_up: bool or None + :param pull_up: + If :data:`True`, the pin will be pulled high with an internal resistor. + If :data:`False` (the default), the pin will be pulled low. If + :data:`None`, the pin will be floating. As gpiozero cannot + automatically guess the active state when not pulling the pin, the + *active_state* parameter must be passed. + + :type active_state: bool or None + :param active_state: + If :data:`True`, when the hardware pin state is ``HIGH``, the software + pin is ``HIGH``. If :data:`False`, the input polarity is reversed: when + the hardware pin state is ``HIGH``, the software pin state is ``LOW``. + Use this parameter to set the active state of the underlying pin when + configuring it as not pulled (when *pull_up* is :data:`None`). When + *pull_up* is :data:`True` or :data:`False`, the active state is + automatically set to the proper value. + + :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, pin=None, *, pull_up=False, active_state=None, + pin_factory=None): + super().__init__(pin, pin_factory=pin_factory) + try: + self.pin.function = 'input' + pull = {None: 'floating', True: 'up', False: 'down'}[pull_up] + if self.pin.pull != pull: + self.pin.pull = pull + except: + self.close() + raise + + if pull_up is None: + if active_state is None: + raise PinInvalidState( + f'Pin {self.pin.info.name} is defined as floating, but ' + f'"active_state" is not defined') + self._active_state = bool(active_state) + else: + if active_state is not None: + raise PinInvalidState( + f'Pin {self.pin.info.name} is not floating, but ' + f'"active_state" is not None') + self._active_state = False if pull_up else True + self._inactive_state = not self._active_state @property def pull_up(self): - return self._pull_up + """ + If :data:`True`, the device uses a pull-up resistor to set the GPIO pin + "high" by default. + """ + pull = self.pin.pull + if pull == 'floating': + return None + else: + return pull == 'up' + + def __repr__(self): + try: + return ( + f"") + except: + return super().__repr__() + + +class DigitalInputDevice(EventsMixin, InputDevice): + """ + Represents a generic input device with typical on/off behaviour. + + This class extends :class:`InputDevice` with machinery to fire the active + and inactive events for devices that operate in a typical digital manner: + straight forward on / off states with (reasonably) clean transitions + between the two. + + :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. + + :type pull_up: bool or None + :param pull_up: + See description under :class:`InputDevice` for more information. + + :type active_state: bool or None + :param active_state: + See description under :class:`InputDevice` for more information. + + :type bounce_time: float or None + :param bounce_time: + Specifies the length of time (in seconds) that the component will + ignore changes in state after an initial change. This defaults to + :data:`None` which indicates that no bounce compensation will be + performed. + + :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, pin=None, *, pull_up=False, active_state=None, + bounce_time=None, pin_factory=None): + super().__init__( + pin, pull_up=pull_up, active_state=active_state, + pin_factory=pin_factory) + try: + self.pin.bounce = bounce_time + self.pin.edges = 'both' + self.pin.when_changed = self._pin_changed + # Call _fire_events once to set initial state of events + self._fire_events(self.pin_factory.ticks(), self.is_active) + except: + self.close() + raise + + def _pin_changed(self, ticks, state): + # XXX This is a bit of a hack; _fire_events takes *is_active* rather + # than *value*. Here we're assuming no-one's overridden the default + # implementation of *is_active*. + self._fire_events(ticks, bool(self._state_to_value(state))) + + +class SmoothedInputDevice(EventsMixin, InputDevice): + """ + Represents a generic input device which takes its value from the average of + a queue of historical values. + + This class extends :class:`InputDevice` with a queue which is filled by a + background thread which continually polls the state of the underlying + device. The average (a configurable function) of the values in the queue is + compared to a threshold which is used to determine the state of the + :attr:`is_active` property. + + .. note:: + + The background queue is not automatically started upon construction. + This is to allow descendents to set up additional components before the + queue starts reading values. Effectively this is an abstract base + class. + + This class is intended for use with devices which either exhibit analog + behaviour (such as the charging time of a capacitor with an LDR), or those + which exhibit "twitchy" behaviour (such as certain motion sensors). + + :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. + + :type pull_up: bool or None + :param pull_up: + See description under :class:`InputDevice` for more information. + + :type active_state: bool or None + :param active_state: + See description under :class:`InputDevice` for more information. + + :param float threshold: + The value above which the device will be considered "on". + + :param int queue_len: + The length of the internal queue which is filled by the background + thread. + :param float sample_wait: + The length of time to wait between retrieving the state of the + underlying device. Defaults to 0.0 indicating that values are retrieved + as fast as possible. -class Button(InputDevice): - pass + :param bool partial: + If :data:`False` (the default), attempts to read the state of the + device (from the :attr:`is_active` property) will block until the queue + has filled. If :data:`True`, a value will be returned immediately, but + be aware that this value is likely to fluctuate excessively. + :param average: + The function used to average the values in the internal queue. This + defaults to :func:`statistics.median` which is a good selection for + discarding outliers from jittery sensors. The function specified must + accept a sequence of numbers and return a single number. -class MotionSensor(InputDevice): + :type ignore: frozenset or None + :param ignore: + The set of values which the queue should ignore, if returned from + querying the device's value. + + :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, pin=None, queue_len=5, sample_rate=10, threshold=0.5, - partial=False): - super(MotionSensor, self).__init__(pin, pull_up=False) - if queue_len < 1: - raise InputDeviceError('queue_len must be at least one') - self.sample_rate = sample_rate - self.threshold = threshold - self.partial = partial - self._queue = deque(maxlen=queue_len) - self._queue_full = Event() - self._queue_thread = GPIOThread(target=self._fill_queue) - self._queue_thread.start() + self, pin=None, *, pull_up=False, active_state=None, threshold=0.5, + queue_len=5, sample_wait=0.0, partial=False, average=median, + ignore=None, pin_factory=None): + self._queue = None + super().__init__( + pin, pull_up=pull_up, active_state=active_state, + pin_factory=pin_factory) + try: + self._queue = GPIOQueue(self, queue_len, sample_wait, partial, + average, ignore) + self.threshold = float(threshold) + except: + self.close() + raise + + def close(self): + try: + self._queue.stop() + except AttributeError: + # If the queue isn't initialized (it's None), or _queue hasn't been + # set ignore the error because we're trying to close anyway + pass + except RuntimeError: + # Cannot join thread before it starts; we don't care about this + # because we're trying to close the thread anyway + pass + self._queue = None + super().close() + + def __repr__(self): + try: + self._check_open() + except DeviceClosed: + return super().__repr__() + else: + if self.partial or self._queue.full.is_set(): + return super().__repr__() + else: + return ( + f"") @property def queue_len(self): - return self._queue.maxlen + """ + The length of the internal queue of values which is averaged to + determine the overall state of the device. This defaults to 5. + """ + self._check_open() + return self._queue.queue.maxlen + + @property + def partial(self): + """ + If :data:`False` (the default), attempts to read the + :attr:`~SmoothedInputDevice.value` or + :attr:`~SmoothedInputDevice.is_active` properties will block until the + queue has filled. + """ + self._check_open() + return self._queue.partial @property def value(self): - if not self.partial: - self._queue_full.wait() - try: - return sum(self._queue) / len(self._queue) - except ZeroDivisionError: - # No data == no motion - return 0.0 + """ + Returns the average of the values in the internal queue. This is + compared to :attr:`~SmoothedInputDevice.threshold` to determine whether + :attr:`is_active` is :data:`True`. + """ + self._check_open() + return self._queue.value + + @property + def threshold(self): + """ + If :attr:`~SmoothedInputDevice.value` exceeds this amount, then + :attr:`is_active` will return :data:`True`. + """ + return self._threshold + + @threshold.setter + def threshold(self, value): + if not (0.0 < value < 1.0): + raise InputDeviceError( + 'threshold must be between zero and one exclusive' + ) + self._threshold = float(value) @property - def motion_detected(self): + def is_active(self): + """ + Returns :data:`True` if the :attr:`~SmoothedInputDevice.value` + currently exceeds :attr:`~SmoothedInputDevice.threshold` and + :data:`False` otherwise. + """ return self.value > self.threshold - def _get_sample_rate(self): - return self._sample_rate - def _set_sample_rate(self, value): - if value <= 0: - raise InputDeviceError('sample_rate must be greater than zero') - self._sample_rate = value - sample_rate = property(_get_sample_rate, _set_sample_rate) - def _get_threshold(self): - return self._threshold - def _set_threshold(self, value): - if value < 0: - raise InputDeviceError('threshold must be zero or more') - self._threshold = value - threshold = property(_get_threshold, _set_threshold) - - def _fill_queue(self): - while ( - not self._queue_thread.stopping.wait(1 / self.sample_rate) and - len(self._queue) < self._queue.maxlen - ): - self._queue.append(self.is_active) - self._queue_full.set() - while not self._queue_thread.stopping.wait(1 / self.sample_rate): - self._queue.append(self.is_active) - - -class LightSensor(InputDevice): - def __init__( - self, pin=None, queue_len=5, darkness_time=0.01, - threshold=0.1, partial=False): - super(LightSensor, self).__init__(pin, pull_up=False) - if queue_len < 1: - raise InputDeviceError('queue_len must be at least one') - self.darkness_time = darkness_time - self.threshold = threshold - self.partial = partial - self._charged = Event() - GPIO.add_event_detect(self.pin, GPIO.RISING, lambda channel: self._charged.set()) - self._queue = deque(maxlen=queue_len) - self._queue_full = Event() - self._queue_thread = GPIOThread(target=self._fill_queue) - self._last_state = None - self._when_light = None - self._when_dark = None - self._when_light_event = Event() - self._when_dark_event = Event() - self._queue_thread.start() +class Button(HoldMixin, DigitalInputDevice): + """ + Extends :class:`DigitalInputDevice` and represents a simple push button + or switch. + + Connect one side of the button to a ground pin, and the other to any GPIO + pin. Alternatively, connect one side of the button to the 3V3 pin, and the + other to any GPIO pin, then set *pull_up* to :data:`False` in the + :class:`Button` constructor. + + The following example will print a line of text when the button is pushed:: + + from gpiozero import Button + + button = Button(4) + button.wait_for_press() + print("The button was pressed!") + + :type pin: int or str + :param pin: + The GPIO pin which the button is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :type pull_up: bool or None + :param pull_up: + If :data:`True` (the default), the GPIO pin will be pulled high by + default. In this case, connect the other side of the button to ground. + If :data:`False`, the GPIO pin will be pulled low by default. In this + case, connect the other side of the button to 3V3. If :data:`None`, the + pin will be floating, so it must be externally pulled up or down and + the ``active_state`` parameter must be set accordingly. + + :type active_state: bool or None + :param active_state: + See description under :class:`InputDevice` for more information. + + :type bounce_time: float or None + :param bounce_time: + If :data:`None` (the default), no software bounce compensation will be + performed. Otherwise, this is the length of time (in seconds) that the + component will ignore changes in state after an initial change. + + :param float hold_time: + The length of time (in seconds) to wait after the button is pushed, + until executing the :attr:`when_held` handler. Defaults to ``1``. + + :param bool hold_repeat: + If :data:`True`, the :attr:`when_held` handler will be repeatedly + executed as long as the device remains active, every *hold_time* + seconds. If :data:`False` (the default) the :attr:`when_held` handler + will be only be executed once per hold. + + :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, pin=None, *, pull_up=True, active_state=None, + bounce_time=None, hold_time=1, hold_repeat=False, + pin_factory=None): + super().__init__( + pin, pull_up=pull_up, active_state=active_state, + bounce_time=bounce_time, pin_factory=pin_factory) + self.hold_time = hold_time + self.hold_repeat = hold_repeat @property - def queue_len(self): - return self._queue.maxlen + def value(self): + """ + Returns 1 if the button is currently pressed, and 0 if it is not. + """ + return super().value + +Button.is_pressed = Button.is_active +Button.pressed_time = Button.active_time +Button.when_pressed = Button.when_activated +Button.when_released = Button.when_deactivated +Button.wait_for_press = Button.wait_for_active +Button.wait_for_release = Button.wait_for_inactive + + +class LineSensor(SmoothedInputDevice): + """ + Extends :class:`SmoothedInputDevice` and represents a single pin line + sensor like the TCRT5000 infra-red proximity sensor found in the `CamJam #3 + EduKit`_. + + A typical line sensor has a small circuit board with three pins: VCC, GND, + and OUT. VCC should be connected to a 3V3 pin, GND to one of the ground + pins, and finally OUT to the GPIO specified as the value of the *pin* + parameter in the constructor. + + The following code will print a line of text indicating when the sensor + detects a line, or stops detecting a line:: + + from gpiozero import LineSensor + from signal import pause + + sensor = LineSensor(4) + sensor.when_line = lambda: print('Line detected') + sensor.when_no_line = lambda: print('No line detected') + pause() + + :type pin: int or str + :param pin: + The GPIO pin which the sensor is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :type pull_up: bool or None + :param pull_up: + See description under :class:`InputDevice` for more information. + + :type active_state: bool or None + :param active_state: + See description under :class:`InputDevice` for more information. + + :param int queue_len: + The length of the queue used to store values read from the sensor. This + defaults to 5. + + :param float sample_rate: + The number of values to read from the device (and append to the + internal queue) per second. Defaults to 100. + + :param float threshold: + Defaults to 0.5. When the average of all values in the internal queue + rises above this value, the sensor will be considered "active" by the + :attr:`~SmoothedInputDevice.is_active` property, and all appropriate + events will be fired. + + :param bool partial: + When :data:`False` (the default), the object will not return a value + for :attr:`~SmoothedInputDevice.is_active` until the internal queue has + filled with values. Only set this to :data:`True` if you require + values immediately after object construction. + + :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). + + .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 + """ + def __init__(self, pin=None, *, pull_up=False, active_state=None, + queue_len=5, sample_rate=100, threshold=0.5, partial=False, + pin_factory=None): + super().__init__( + pin, pull_up=pull_up, active_state=active_state, + threshold=threshold, queue_len=queue_len, + sample_wait=1 / sample_rate, partial=partial, + pin_factory=pin_factory) + self._queue.start() @property def value(self): - if not self.partial: - self._queue_full.wait() - try: - return 1.0 - (sum(self._queue) / len(self._queue)) / self.darkness_time - except ZeroDivisionError: - # No data == no light - return 0.0 + """ + Returns a value representing the average of the queued values. This + is nearer 0 for black under the sensor, and nearer 1 for white under + the sensor. + """ + return super().value @property - def light_detected(self): - return self.value > self.threshold + def line_detected(self): + return not self.is_active - def _get_when_light(self): - return self._when_light - def _set_when_light(self, value): - if not callable(value) and value is not None: - raise InputDeviceError('when_light must be None or a function') - self._when_light = value - when_light = property(_get_when_light, _set_when_light) - - def _get_when_dark(self): - return self._when_dark - def _set_when_dark(self, value): - if not callable(value) and value is not None: - raise InputDeviceError('when_dark must be None or a function') - self._when_dark = value - - def wait_for_light(self, timeout=None): - self._when_light_event.wait(timeout) - - def wait_for_dark(self, timeout=None): - self._when_dark_event.wait(timeout) - - def _get_darkness_time(self): - return self._darkness_time - def _set_darkness_time(self, value): - if value <= 0.0: - raise InputDeviceError('darkness_time must be greater than zero') - self._darkness_time = value - # XXX Empty the queue and restart the thread - darkness_time = property(_get_darkness_time, _set_darkness_time) - - def _get_threshold(self): - return self._threshold - def _set_threshold(self, value): - if value < 0: - raise InputDeviceError('threshold must be zero or more') - self._threshold = value - threshold = property(_get_threshold, _set_threshold) +LineSensor.when_line = LineSensor.when_deactivated +LineSensor.when_no_line = LineSensor.when_activated +LineSensor.wait_for_line = LineSensor.wait_for_inactive +LineSensor.wait_for_no_line = LineSensor.wait_for_active + + +class MotionSensor(SmoothedInputDevice): + """ + Extends :class:`SmoothedInputDevice` and represents a passive infra-red + (PIR) motion sensor like the sort found in the `CamJam #2 EduKit`_. + + .. _CamJam #2 EduKit: http://camjam.me/?page_id=623 + + A typical PIR device has a small circuit board with three pins: VCC, OUT, + and GND. VCC should be connected to a 5V pin, GND to one of the ground + pins, and finally OUT to the GPIO specified as the value of the *pin* + parameter in the constructor. + + The following code will print a line of text when motion is detected:: + + from gpiozero import MotionSensor - def _fill_queue(self): + pir = MotionSensor(4) + pir.wait_for_motion() + print("Motion detected!") + + :type pin: int or str + :param pin: + The GPIO pin which the sensor is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :type pull_up: bool or None + :param pull_up: + See description under :class:`InputDevice` for more information. + + :type active_state: bool or None + :param active_state: + See description under :class:`InputDevice` for more information. + + :param int queue_len: + The length of the queue used to store values read from the sensor. This + defaults to 1 which effectively disables the queue. If your motion + sensor is particularly "twitchy" you may wish to increase this value. + + :param float sample_rate: + The number of values to read from the device (and append to the + internal queue) per second. Defaults to 10. + + :param float threshold: + Defaults to 0.5. When the average of all values in the internal queue + rises above this value, the sensor will be considered "active" by the + :attr:`~SmoothedInputDevice.is_active` property, and all appropriate + events will be fired. + + :param bool partial: + When :data:`False` (the default), the object will not return a value + for :attr:`~SmoothedInputDevice.is_active` until the internal queue has + filled with values. Only set this to :data:`True` if you require + values immediately after object construction. + + :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, pin=None, *, pull_up=False, active_state=None, + queue_len=1, sample_rate=10, threshold=0.5, partial=False, + pin_factory=None): + super().__init__( + pin, pull_up=pull_up, active_state=active_state, + threshold=threshold, queue_len=queue_len, sample_wait=1 / + sample_rate, partial=partial, pin_factory=pin_factory, average=mean) + self._queue.start() + + @property + def value(self): + """ + With the default *queue_len* of 1, this is effectively boolean where 0 + means no motion detected and 1 means motion detected. If you specify + a *queue_len* greater than 1, this will be an averaged value where + values closer to 1 imply motion detection. + """ + return super().value + +MotionSensor.motion_detected = MotionSensor.is_active +MotionSensor.when_motion = MotionSensor.when_activated +MotionSensor.when_no_motion = MotionSensor.when_deactivated +MotionSensor.wait_for_motion = MotionSensor.wait_for_active +MotionSensor.wait_for_no_motion = MotionSensor.wait_for_inactive + + +class LightSensor(SmoothedInputDevice): + """ + Extends :class:`SmoothedInputDevice` and represents a light dependent + resistor (LDR). + + Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µF + capacitor to a ground pin; connect the other leg of the LDR and the other + leg of the capacitor to the same GPIO pin. This class repeatedly discharges + the capacitor, then times the duration it takes to charge (which will vary + according to the light falling on the LDR). + + The following code will print a line of text when light is detected:: + + from gpiozero import LightSensor + + ldr = LightSensor(18) + ldr.wait_for_light() + print("Light detected!") + + :type pin: int or str + :param pin: + The GPIO pin which the sensor is attached to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param int queue_len: + The length of the queue used to store values read from the circuit. + This defaults to 5. + + :param float charge_time_limit: + If the capacitor in the circuit takes longer than this length of time + to charge, it is assumed to be dark. The default (0.01 seconds) is + appropriate for a 1µF capacitor coupled with the LDR from the + `CamJam #2 EduKit`_. You may need to adjust this value for different + valued capacitors or LDRs. + + :param float threshold: + Defaults to 0.1. When the average of all values in the internal queue + rises above this value, the area will be considered "light", and all + appropriate events will be fired. + + :param bool partial: + When :data:`False` (the default), the object will not return a value + for :attr:`~SmoothedInputDevice.is_active` until the internal queue has + filled with values. Only set this to :data:`True` if you require + values immediately after object construction. + + :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). + + .. _CamJam #2 EduKit: http://camjam.me/?page_id=623 + """ + def __init__(self, pin=None, *, queue_len=5, charge_time_limit=0.01, + threshold=0.1, partial=False, pin_factory=None): + super().__init__( + pin, pull_up=False, threshold=threshold, queue_len=queue_len, + sample_wait=0.0, partial=partial, pin_factory=pin_factory) try: - while ( - not self._queue_thread.stopping.is_set() and - len(self._queue) < self._queue.maxlen - ): - self._queue.append(self._time_charging()) - if self.partial: - self._fire_events() - self._queue_full.set() - while not self._queue_thread.stopping.is_set(): - self._queue.append(self._time_charging()) - self._fire_events() - finally: - GPIO.remove_event_detect(self.pin) - - def _time_charging(self): + self._charge_time_limit = charge_time_limit + self._charge_time = None + self._charged = Event() + self.pin.edges = 'rising' + self.pin.bounce = None + self.pin.when_changed = self._cap_charged + self._queue.start() + except: + self.close() + raise + + @property + def charge_time_limit(self): + return self._charge_time_limit + + def _cap_charged(self, ticks, state): + self._charge_time = ticks + self._charged.set() + + def _read(self): # Drain charge from the capacitor - GPIO.setup(self.pin, GPIO.OUT) - GPIO.output(self.pin, GPIO.LOW) + self.pin.function = 'output' + self.pin.state = False sleep(0.1) - # Time the charging of the capacitor - start = time() + self._charge_time = None self._charged.clear() - GPIO.setup(self.pin, GPIO.IN) - self._charged.wait(self.darkness_time) - return min(self.darkness_time, time() - start) - - def _fire_events(self): - last_state = self._last_state - self._last_state = state = self.light_detected - if not last_state and state: - self._when_dark_event.clear() - self._when_light_event.set() - if self.when_light: - self.when_light() - elif last_state and not state: - self._when_light_event.clear() - self._when_dark_event.set() - if self.when_dark: - self.when_dark() - -class TemperatureSensor(W1ThermSensor): + # Time the charging of the capacitor + start = self.pin_factory.ticks() + self.pin.function = 'input' + self._charged.wait(self.charge_time_limit) + if self._charge_time is None: + return 0.0 + else: + return 1.0 - min(1.0, + (self.pin_factory.ticks_diff(self._charge_time, start) / + self.charge_time_limit)) + + @property + def value(self): + """ + Returns a value between 0 (dark) and 1 (light). + """ + return super().value + +LightSensor.light_detected = LightSensor.is_active +LightSensor.when_light = LightSensor.when_activated +LightSensor.when_dark = LightSensor.when_deactivated +LightSensor.wait_for_light = LightSensor.wait_for_active +LightSensor.wait_for_dark = LightSensor.wait_for_inactive + + +class DistanceSensor(SmoothedInputDevice): + """ + Extends :class:`SmoothedInputDevice` and represents an HC-SR04 ultrasonic + distance sensor, as found in the `CamJam #3 EduKit`_. + + The distance sensor requires two GPIO pins: one for the *trigger* (marked + TRIG on the sensor) and another for the *echo* (marked ECHO on the sensor). + However, a voltage divider is required to ensure the 5V from the ECHO pin + doesn't damage the Pi. Wire your sensor according to the following + instructions: + + 1. Connect the GND pin of the sensor to a ground pin on the Pi. + + 2. Connect the TRIG pin of the sensor a GPIO pin. + + 3. Connect one end of a 330Ω resistor to the ECHO pin of the sensor. + + 4. Connect one end of a 470Ω resistor to the GND pin of the sensor. + + 5. Connect the free ends of both resistors to another GPIO pin. This forms + the required `voltage divider`_. + + 6. Finally, connect the VCC pin of the sensor to a 5V pin on the Pi. + + Alternatively, the 3V3 tolerant HC-SR04P sensor (which does not require a + voltage divider) will work with this class. + + + .. note:: + + If you do not have the precise values of resistor specified above, + don't worry! What matters is the *ratio* of the resistors to each + other. + + You also don't need to be absolutely precise; the `voltage divider`_ + given above will actually output ~3V (rather than 3.3V). A simple 2:3 + ratio will give 3.333V which implies you can take three resistors of + equal value, use one of them instead of the 330Ω resistor, and two of + them in series instead of the 470Ω resistor. + + .. _voltage divider: https://en.wikipedia.org/wiki/Voltage_divider + + The following code will periodically report the distance measured by the + sensor in cm assuming the TRIG pin is connected to GPIO17, and the ECHO + pin to GPIO18:: + + from gpiozero import DistanceSensor + from time import sleep + + sensor = DistanceSensor(echo=18, trigger=17) + while True: + print('Distance: ', sensor.distance * 100) + sleep(1) + + .. note:: + + For improved accuracy, use the pigpio pin driver rather than the default + RPi.GPIO driver (pigpio uses DMA sampling for much more precise edge + timing). This is particularly relevant if you're using Pi 1 or Pi Zero. + See :ref:`changing-pin-factory` for further information. + + :type echo: int or str + :param echo: + The GPIO pin which the ECHO pin is connected to. See + :ref:`pin-numbering` for valid pin numbers. If this is :data:`None` a + :exc:`GPIODeviceError` will be raised. + + :type trigger: int or str + :param trigger: + The GPIO pin which the TRIG pin is connected to. See + :ref:`pin-numbering` for valid pin numbers. If this is :data:`None` a + :exc:`GPIODeviceError` will be raised. + + :param int queue_len: + The length of the queue used to store values read from the sensor. + This defaults to 9. + + :param float max_distance: + The :attr:`value` attribute reports a normalized value between 0 (too + close to measure) and 1 (maximum distance). This parameter specifies + the maximum distance expected in meters. This defaults to 1. + + :param float threshold_distance: + Defaults to 0.3. This is the distance (in meters) that will trigger the + ``in_range`` and ``out_of_range`` events when crossed. + + :param bool partial: + When :data:`False` (the default), the object will not return a value + for :attr:`~SmoothedInputDevice.is_active` until the internal queue has + filled with values. Only set this to :data:`True` if you require + values immediately after object construction. + + :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). + + .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 + """ + ECHO_LOCK = Lock() + + def __init__(self, echo=None, trigger=None, *, queue_len=9, + max_distance=1, threshold_distance=0.3, partial=False, + pin_factory=None): + self._trigger = None + super().__init__( + echo, pull_up=False, queue_len=queue_len, sample_wait=0.06, + partial=partial, ignore=frozenset({None}), pin_factory=pin_factory + ) + try: + if max_distance <= 0: + raise ValueError('invalid maximum distance (must be positive)') + self._max_distance = max_distance + self.threshold = threshold_distance / max_distance + self.speed_of_sound = 343.26 # m/s + self._trigger = GPIODevice(trigger, pin_factory=pin_factory) + self._echo = Event() + self._echo_rise = None + self._echo_fall = None + self._trigger.pin.function = 'output' + self._trigger.pin.state = False + self.pin.edges = 'both' + self.pin.bounce = None + self.pin.when_changed = self._echo_changed + self._queue.start() + except: + self.close() + raise + + if PiGPIOFactory is None or not isinstance(self.pin_factory, PiGPIOFactory): + warnings.warn(PWMSoftwareFallback( + 'For more accurate readings, use the pigpio pin factory.' + 'See https://gpiozero.readthedocs.io/en/stable/api_input.html#distancesensor-hc-sr04 for more info' + )) + + def close(self): + try: + self._trigger.close() + except AttributeError: + pass + self._trigger = None + super().close() + + @property + def max_distance(self): + """ + The maximum distance that the sensor will measure in meters. This value + is specified in the constructor and is used to provide the scaling for + the :attr:`~SmoothedInputDevice.value` attribute. When :attr:`distance` + is equal to :attr:`max_distance`, :attr:`~SmoothedInputDevice.value` + will be 1. + """ + return self._max_distance + + @max_distance.setter + def max_distance(self, value): + if value <= 0: + raise ValueError('invalid maximum distance (must be positive)') + t = self.threshold_distance + self._max_distance = value + self.threshold_distance = t + + @property + def threshold_distance(self): + """ + The distance, measured in meters, that will trigger the + :attr:`when_in_range` and :attr:`when_out_of_range` events when + crossed. This is simply a meter-scaled variant of the usual + :attr:`~SmoothedInputDevice.threshold` attribute. + """ + return self.threshold * self.max_distance + + @threshold_distance.setter + def threshold_distance(self, value): + self.threshold = value / self.max_distance + + @property + def distance(self): + """ + Returns the current distance measured by the sensor in meters. Note + that this property will have a value between 0 and + :attr:`max_distance`. + """ + return self.value * self._max_distance + + @property + def value(self): + """ + Returns a value between 0, indicating the reflector is either touching + the sensor or is sufficiently near that the sensor can't tell the + difference, and 1, indicating the reflector is at or beyond the + specified *max_distance*. + """ + return super().value + + @property + def trigger(self): + """ + Returns the :class:`Pin` that the sensor's trigger is connected to. + """ + return self._trigger.pin + + @property + def echo(self): + """ + Returns the :class:`Pin` that the sensor's echo is connected to. This + is simply an alias for the usual :attr:`~GPIODevice.pin` attribute. + """ + return self.pin + + def _echo_changed(self, ticks, level): + if level: + self._echo_rise = ticks + else: + self._echo_fall = ticks + self._echo.set() + + def _read(self): + # Wait up to 50ms for the echo pin to fall to low (the maximum echo + # pulse is 35ms so this gives some leeway); if it doesn't something is + # horribly wrong (most likely at the hardware level) + if self.pin.state: + if not self._echo.wait(0.05): + warnings.warn(DistanceSensorNoEcho('echo pin set high')) + return None + self._echo.clear() + self._echo_fall = None + self._echo_rise = None + # Obtain the class-level ECHO_LOCK to ensure multiple distance sensors + # don't listen for each other's "pings" + with DistanceSensor.ECHO_LOCK: + # Fire the trigger + self._trigger.pin.state = True + sleep(0.00001) + self._trigger.pin.state = False + # Wait up to 100ms for the echo pin to rise and fall (35ms is the + # maximum pulse time, but the pre-rise time is unspecified in the + # "datasheet"; 100ms seems sufficiently long to conclude something + # has failed) + if self._echo.wait(0.1): + if self._echo_fall is not None and self._echo_rise is not None: + distance = ( + self.pin_factory.ticks_diff( + self._echo_fall, self._echo_rise) * + self.speed_of_sound / 2.0) + return min(1.0, distance / self._max_distance) + else: + # If we only saw the falling edge it means we missed + # the echo because it was too fast + return None + else: + # The echo pin never rose or fell; something's gone horribly + # wrong + warnings.warn(DistanceSensorNoEcho('no echo received')) + return None + + @property + def in_range(self): + return not self.is_active + +DistanceSensor.when_out_of_range = DistanceSensor.when_activated +DistanceSensor.when_in_range = DistanceSensor.when_deactivated +DistanceSensor.wait_for_out_of_range = DistanceSensor.wait_for_active +DistanceSensor.wait_for_in_range = DistanceSensor.wait_for_inactive + + +class RotaryEncoder(EventsMixin, CompositeDevice): + """ + Represents a simple two-pin incremental `rotary encoder`_ device. + + These devices typically have three pins labelled "A", "B", and "C". Connect + A and B directly to two GPIO pins, and C ("common") to one of the ground + pins on your Pi. Then simply specify the A and B pins as the arguments when + constructing this classs. + + For example, if your encoder's A pin is connected to GPIO 21, and the B + pin to GPIO 20 (and presumably the C pin to a suitable GND pin), while an + LED (with a suitable 300Ω resistor) is connected to GPIO 5, the following + session will result in the brightness of the LED being controlled by + dialling the rotary encoder back and forth:: + + >>> from gpiozero import RotaryEncoder + >>> from gpiozero.tools import scaled_half + >>> rotor = RotaryEncoder(21, 20) + >>> led = PWMLED(5) + >>> led.source = scaled_half(rotor.values) + + :type a: int or str + :param a: + The GPIO pin connected to the "A" output of the rotary encoder. + + :type b: int or str + :param b: + The GPIO pin connected to the "B" output of the rotary encoder. + + :type bounce_time: float or None + :param bounce_time: + If :data:`None` (the default), no software bounce compensation will be + performed. Otherwise, this is the length of time (in seconds) that the + component will ignore changes in state after an initial change. + + :type max_steps: int + :param max_steps: + The number of steps clockwise the encoder takes to change the + :attr:`value` from 0 to 1, or counter-clockwise from 0 to -1. + If this is 0, then the encoder's :attr:`value` never changes, but you + can still read :attr:`steps` to determine the integer number of steps + the encoder has moved clockwise or counter clockwise. + + :type threshold_steps: tuple of int + :param threshold_steps: + A (min, max) tuple of steps between which the device will be considered + "active", inclusive. In other words, when :attr:`steps` is greater than + or equal to the *min* value, and less than or equal the *max* value, + the :attr:`active` property will be :data:`True` and the appropriate + events (:attr:`when_activated`, :attr:`when_deactivated`) will be + fired. Defaults to (0, 0). + + :type wrap: bool + :param wrap: + If :data:`True` and *max_steps* is non-zero, when the :attr:`steps` + reaches positive or negative *max_steps* it wraps around by negation. + Defaults to :data:`False`. + + :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). + + .. _rotary encoder: https://en.wikipedia.org/wiki/Rotary_encoder + """ + # The rotary encoder's two pins move through the following sequence when + # the encoder is rotated one step clockwise: + # + # ────┐ ┌─────┐ ┌──────── + # _ │ │ │ │ counter ┌───┐ + # A │ │ │ │ clockwise ┌─── │ 0 │ ───┐ clockwise + # └─────┘ └─────┘ (CCW) │ └───┘ │ (CW) + # : : : : │ ┌───┐ ┌───┐ │ + # ───────┐ : ┌─────┐ : ┌───── ▾ │ 1 │ │ 2 │ ▾ + # _ : │ : │ : │ : │ └───┘ └───┘ + # B : │ : │ : │ : │ │ ┌───┐ │ + # : └─────┘ : └─────┘ └─── │ 3 │ ───┘ + # : : : : : : : : └───┘ + # 0 2 3 1 0 2 3 1 0 + # + # Treating the A pin as a "high" bit, and the B pin as a "low" bit, this + # means that the pins return the sequence 0, 2, 3, 1 for each step that the + # encoder takes clockwise. Conversely, the pins return the sequence 0, 1, + # 3, 2 for each step counter-clockwise. + # + # We can treat these values as edges to take in a simple state machine, + # which is represented in the dictionary below: + + TRANSITIONS = { + 'idle': ['idle', 'ccw1', 'cw1', 'idle'], + 'ccw1': ['idle', 'ccw1', 'ccw3', 'ccw2'], + 'ccw2': ['idle', 'ccw1', 'ccw3', 'ccw2'], + 'ccw3': ['-1', 'idle', 'ccw3', 'ccw2'], + 'cw1': ['idle', 'cw3', 'cw1', 'cw2'], + 'cw2': ['idle', 'cw3', 'cw1', 'cw2'], + 'cw3': ['+1', 'cw3', 'idle', 'cw2'], + } + + # The state machine here includes more than just the strictly necessary + # edges; it also permits "wiggle" between intermediary states so that the + # overall graph looks like this: + # + # ┌──────┐ + # │ │ + # ┌─────┤ idle ├────┐ + # │1 │ │ 2│ + # │ └──────┘ │ + # ▾ ▴ ▴ ▾ + # ┌────────┐ │ │ ┌───────┐ + # │ │ 0│ │0 │ │ + # ┌───┤ ccw1 ├──┤ ├──┤ cw1 ├───┐ + # │2 │ │ │ │ │ │ 1│ + # │ └─┬──────┘ │ │ └─────┬─┘ │ + # │ 3│ ▴ │ │ ▴ │3 │ + # │ ▾ │1 │ │ 2│ ▾ │ + # │ ┌──────┴─┐ │ │ ┌─┴─────┐ │ + # │ │ │ 0│ │0 │ │ │ + # │ │ ccw2 ├──┤ ├──┤ cw2 │ │ + # │ │ │ │ │ │ │ │ + # │ └─┬──────┘ │ │ └─────┬─┘ │ + # │ 2│ ▴ │ │ ▴ │1 │ + # │ ▾ │3 │ │ 3│ ▾ │ + # │ ┌──────┴─┐ │ │ ┌─┴─────┐ │ + # │ │ │ │ │ │ │ │ + # └──▸│ ccw3 │ │ │ │ cw3 │◂──┘ + # │ │ │ │ │ │ + # └───┬────┘ │ │ └───┬───┘ + # 0│ │ │ │0 + # ▾ │ │ ▾ + # ┌────────┐ │ │ ┌───────┐ + # │ │ │ │ │ │ + # │ -1 ├──┘ └──┤ +1 │ + # │ │ │ │ + # └────────┘ └───────┘ + # + # Note that, once we start down the clockwise (cw) or counter-clockwise + # (ccw) path, we don't allow the state to pick the alternate direction + # without passing through the idle state again. This seems to work well in + # practice with several encoders, even quite jiggly ones with no debounce + # hardware or software + + def __init__(self, a, b, *, bounce_time=None, max_steps=16, + threshold_steps=(0, 0), wrap=False, pin_factory=None): + min_thresh, max_thresh = threshold_steps + if max_thresh < min_thresh: + raise ValueError('maximum threshold cannot be less than minimum') + self._steps = 0 + self._max_steps = int(max_steps) + self._threshold = (int(min_thresh), int(max_thresh)) + self._wrap = bool(wrap) + self._state = 'idle' + self._edge = 0 + self._when_rotated = None + self._when_rotated_cw = None + self._when_rotated_ccw = None + self._rotate_event = Event() + self._rotate_cw_event = Event() + self._rotate_ccw_event = Event() + super().__init__( + a=InputDevice(a, pull_up=True, pin_factory=pin_factory), + b=InputDevice(b, pull_up=True, pin_factory=pin_factory), + _order=('a', 'b'), pin_factory=pin_factory) + self.a.pin.bounce_time = bounce_time + self.b.pin.bounce_time = bounce_time + self.a.pin.edges = 'both' + self.b.pin.edges = 'both' + self.a.pin.when_changed = self._a_changed + self.b.pin.when_changed = self._b_changed + # Call _fire_events once to set initial state of events + self._fire_events(self.pin_factory.ticks(), self.is_active) + + def __repr__(self): + try: + self._check_open() + return ( + f"") + except DeviceClosed: + return super().__repr__() + + def _a_changed(self, ticks, state): + edge = (self.a._state_to_value(state) << 1) | (self._edge & 0x1) + self._change_state(ticks, edge) + + def _b_changed(self, ticks, state): + edge = (self._edge & 0x2) | self.b._state_to_value(state) + self._change_state(ticks, edge) + + def _change_state(self, ticks, edge): + self._edge = edge + new_state = RotaryEncoder.TRANSITIONS[self._state][edge] + if new_state == '+1': + self._steps = ( + self._steps + 1 + if not self._max_steps or self._steps < self._max_steps else + -self._max_steps if self._wrap else self._max_steps + ) + self._rotate_cw_event.set() + self._fire_rotated_cw() + self._rotate_cw_event.clear() + elif new_state == '-1': + self._steps = ( + self._steps - 1 + if not self._max_steps or self._steps > -self._max_steps else + self._max_steps if self._wrap else -self._max_steps + ) + self._rotate_ccw_event.set() + self._fire_rotated_ccw() + self._rotate_ccw_event.clear() + else: + self._state = new_state + return + self._rotate_event.set() + self._fire_rotated() + self._rotate_event.clear() + self._fire_events(ticks, self.is_active) + self._state = 'idle' + + def wait_for_rotate(self, timeout=None): + """ + Pause the script until the encoder is rotated at least one step in + either direction, 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 + encoder is rotated. + """ + return self._rotate_event.wait(timeout) + + def wait_for_rotate_clockwise(self, timeout=None): + """ + Pause the script until the encoder is rotated at least one step + clockwise, 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 + encoder is rotated clockwise. + """ + return self._rotate_cw_event.wait(timeout) + + def wait_for_rotate_counter_clockwise(self, timeout=None): + """ + Pause the script until the encoder is rotated at least one step + counter-clockwise, 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 + encoder is rotated counter-clockwise. + """ + return self._rotate_ccw_event.wait(timeout) + + when_rotated = event( + """ + The function to be run when the encoder is rotated in either direction. + + 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. + """) + + when_rotated_clockwise = event( + """ + The function to be run when the encoder is rotated clockwise. + + 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. + """) + + when_rotated_counter_clockwise = event( + """ + The function to be run when the encoder is rotated counter-clockwise. + + 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 steps(self): + """ + The "steps" value of the encoder starts at 0. It increments by one for + every step the encoder is rotated clockwise, and decrements by one for + every step it is rotated counter-clockwise. The steps value is + limited by :attr:`max_steps`. It will not advance beyond positive or + negative :attr:`max_steps`, unless :attr:`wrap` is :data:`True` in + which case it will roll around by negation. If :attr:`max_steps` is + zero then steps are not limited at all, and will increase infinitely + in either direction, but :attr:`value` will return a constant zero. + + Note that, in contrast to most other input devices, because the rotary + encoder has no absolute position the :attr:`steps` attribute (and + :attr:`value` by corollary) is writable. + """ + return self._steps + + def _fire_rotated(self): + if self.when_rotated: + self.when_rotated() + + def _fire_rotated_cw(self): + if self.when_rotated_clockwise: + self.when_rotated_clockwise() + + def _fire_rotated_ccw(self): + if self.when_rotated_counter_clockwise: + self.when_rotated_counter_clockwise() + + @steps.setter + def steps(self, value): + value = int(value) + if self._max_steps: + value = max(-self._max_steps, min(self._max_steps, value)) + self._steps = value + @property def value(self): - return self.get_temperature() + """ + Represents the value of the rotary encoder as a value between -1 and 1. + The value is calculated by dividing the value of :attr:`steps` into the + range from negative :attr:`max_steps` to positive :attr:`max_steps`. + + Note that, in contrast to most other input devices, because the rotary + encoder has no absolute position the :attr:`value` attribute is + writable. + """ + try: + return self._steps / self._max_steps + except ZeroDivisionError: + return 0 + + @value.setter + def value(self, value): + self._steps = int(max(-1, min(1, float(value))) * self._max_steps) + @property + def is_active(self): + return self._threshold[0] <= self._steps <= self._threshold[1] + + @property + def max_steps(self): + """ + The number of discrete steps the rotary encoder takes to move + :attr:`value` from 0 to 1 clockwise, or 0 to -1 counter-clockwise. In + another sense, this is also the total number of discrete states this + input can represent. + """ + return self._max_steps + @property + def threshold_steps(self): + """ + The mininum and maximum number of steps between which :attr:`is_active` + will return :data:`True`. Defaults to (0, 0). + """ + return self._threshold + + @property + def wrap(self): + """ + If :data:`True`, when :attr:`value` reaches its limit (-1 or 1), it + "wraps around" to the opposite limit. When :data:`False`, the value + (and the corresponding :attr:`steps` attribute) simply don't advance + beyond their limits. + """ + return self._wrap diff --git a/gpiozero/internal_devices.py b/gpiozero/internal_devices.py new file mode 100644 index 000000000..370bc3939 --- /dev/null +++ b/gpiozero/internal_devices.py @@ -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 +# Copyright (c) 2017-2021 Ben Nuttall +# Copyright (c) 2019 Jeevan M R <14.jeevan@gmail.com> +# Copyright (c) 2019 Andrew Scheller +# +# 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"" + except DeviceClosed: + return f"" + + +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'' + 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'') + 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'') + 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'') + 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'') + 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. + """) diff --git a/gpiozero/mixins.py b/gpiozero/mixins.py new file mode 100644 index 000000000..1b3b34ac2 --- /dev/null +++ b/gpiozero/mixins.py @@ -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 +# Copyright (c) 2018-2021 Ben Nuttall +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016 Andrew Scheller +# +# 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 diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index d219c6224..9680f15ef 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -1,60 +1,1783 @@ -from RPi import GPIO +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2023 Dave Jones +# Copyright (c) 2022 gnicki2000 <89583687+gnicki2000@users.noreply.github.com> +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2015-2020 Ben Nuttall +# Copyright (c) 2019 tuftii <3215045+tuftii@users.noreply.github.com> +# Copyright (c) 2019 tuftii +# Copyright (c) 2019 Yisrael Dov Lebow 🐻 +# Copyright (c) 2019 Kosovan Sofiia +# Copyright (c) 2016-2019 Andrew Scheller +# Copyright (c) 2016 Ian Harcombe +# +# SPDX-License-Identifier: BSD-3-Clause -from .devices import GPIODeviceError, GPIODevice, GPIOThread +import warnings +from threading import Lock +from itertools import repeat, cycle, chain +from colorzero import Color +from collections import OrderedDict +from math import log2 +from .exc import ( + OutputDeviceBadValue, + GPIOPinMissing, + PWMSoftwareFallback, + DeviceClosed, +) +from .devices import GPIODevice, Device, CompositeDevice +from .mixins import SourceMixin +from .threads import GPIOThread +from .tones import Tone +try: + from .pins.pigpio import PiGPIOFactory +except ImportError: + PiGPIOFactory = None -class OutputDeviceError(GPIODeviceError): - pass +class OutputDevice(SourceMixin, GPIODevice): + """ + Represents a generic GPIO output device. + + This class extends :class:`GPIODevice` to add facilities common to GPIO + output devices: an :meth:`on` method to switch the device on, a + corresponding :meth:`off` method, and a :meth:`toggle` method. + + :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. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the GPIO + to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to + LOW (the :meth:`off` method always does the opposite). + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), the device will be off initially. If + :data:`None`, the device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. -class OutputDevice(GPIODevice): - def __init__(self, pin=None): - super(OutputDevice, self).__init__(pin) - GPIO.setup(pin, GPIO.OUT) + :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, pin=None, *, active_high=True, initial_value=False, + pin_factory=None): + super().__init__(pin, pin_factory=pin_factory) + self._lock = Lock() + self.active_high = active_high + if initial_value is None: + self.pin.function = 'output' + else: + self.pin.output_with_state(self._value_to_state(initial_value)) + + def _value_to_state(self, value): + return bool(self._active_state if value else self._inactive_state) + + def _write(self, value): + try: + self.pin.state = self._value_to_state(value) + except AttributeError: + self._check_open() + raise def on(self): - GPIO.output(self.pin, True) + """ + Turns the device on. + """ + self._write(True) def off(self): - GPIO.output(self.pin, False) + """ + Turns the device off. + """ + self._write(False) + + def toggle(self): + """ + Reverse the state of the device. If it's on, turn it off; if it's off, + turn it on. + """ + with self._lock: + if self.is_active: + self.off() + else: + self.on() + + @property + def value(self): + """ + Returns 1 if the device is currently active and 0 otherwise. Setting + this property changes the state of the device. + """ + return super().value + + @value.setter + def value(self, value): + self._write(value) + + @property + def active_high(self): + """ + When :data:`True`, the :attr:`value` property is :data:`True` when the + device's :attr:`~GPIODevice.pin` is high. When :data:`False` the + :attr:`value` property is :data:`True` when the device's pin is low + (i.e. the value is inverted). + + This property can be set after construction; be warned that changing it + will invert :attr:`value` (i.e. changing this property doesn't change + the device's pin state - it just changes how that state is + interpreted). + """ + return self._active_state + + @active_high.setter + def active_high(self, value): + self._active_state = True if value else False + self._inactive_state = False if value else True + + def __repr__(self): + try: + return ( + f'') + except: + return super().__repr__() + + +class DigitalOutputDevice(OutputDevice): + """ + Represents a generic output device with typical on/off behaviour. + + This class extends :class:`OutputDevice` with a :meth:`blink` method which + uses an optional background thread to handle toggling the device state + without further interaction. + + :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. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the GPIO + to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to + LOW (the :meth:`off` method always does the opposite). + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), the device will be off initially. If + :data:`None`, the device will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the device will be switched on initially. -class LED(OutputDevice): - def __init__(self, pin=None): - super(LED, self).__init__(pin) + :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, pin=None, *, active_high=True, initial_value=False, + pin_factory=None): self._blink_thread = None + self._controller = None + super().__init__(pin, active_high=active_high, + initial_value=initial_value, pin_factory=pin_factory) - def blink(self, on_time, off_time): + @property + def value(self): + return super().value + + @value.setter + def value(self, value): + self._stop_blink() + self._write(value) + + def close(self): + self._stop_blink() + super().close() + + def on(self): self._stop_blink() - self._blink_thread = GPIOThread(target=self._blink_led, args=(on_time, off_time)) + self._write(True) + + def off(self): + self._stop_blink() + self._write(False) + + def blink(self, on_time=1, off_time=1, n=None, background=True): + """ + Make the device turn on and off repeatedly. + + :param float on_time: + Number of seconds on. Defaults to 1 second. + + :param float off_time: + Number of seconds off. Defaults to 1 second. + + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True` (the default), start a background thread to + continue blinking and return immediately. If :data:`False`, only + return when the blink is finished (warning: the default value of + *n* will result in this method never returning). + """ + self._stop_blink() + self._blink_thread = GPIOThread( + self._blink_device, (on_time, off_time, n)) self._blink_thread.start() + if not background: + self._blink_thread.join() + self._blink_thread = None def _stop_blink(self): - if self._blink_thread: + if getattr(self, '_controller', None): + self._controller._stop_blink(self) + self._controller = None + if getattr(self, '_blink_thread', None): self._blink_thread.stop() - self._blink_thread = None + self._blink_thread = None - def _blink_led(self, on_time, off_time): - while True: - super(LED, self).on() + def _blink_device(self, on_time, off_time, n): + iterable = repeat(0) if n is None else repeat(0, n) + for _ in iterable: + self._write(True) if self._blink_thread.stopping.wait(on_time): break - super(LED, self).off() + self._write(False) if self._blink_thread.stopping.wait(off_time): break + +class LED(DigitalOutputDevice): + """ + Extends :class:`DigitalOutputDevice` and represents a light emitting diode + (LED). + + Connect the cathode (short leg, flat side) of the LED to a ground pin; + connect the anode (longer leg) to a limiting resistor; connect the other + side of the limiting resistor to a GPIO pin (the limiting resistor can be + placed either side of the LED). + + The following example will light the LED:: + + from gpiozero import LED + + led = LED(17) + led.on() + + :type pin: int or str + :param pin: + The GPIO pin which the LED is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param bool active_high: + If :data:`True` (the default), the LED will operate normally with the + circuit described above. If :data:`False` you should wire the cathode + to the GPIO pin, and the anode to a 3V3 pin (via a limiting resistor). + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), the LED will be off initially. If + :data:`None`, the LED will be left in whatever state the pin is found + in when configured for output (warning: this can be on). If + :data:`True`, the LED will be switched on initially. + + :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). + """ + pass + +LED.is_lit = LED.is_active + + +class Buzzer(DigitalOutputDevice): + """ + Extends :class:`DigitalOutputDevice` and represents a digital buzzer + component. + + .. note:: + + This interface is only capable of simple on/off commands, and is not + capable of playing a variety of tones (see :class:`TonalBuzzer`). + + Connect the cathode (negative pin) of the buzzer to a ground pin; connect + the other side to any GPIO pin. + + The following example will sound the buzzer:: + + from gpiozero import Buzzer + + bz = Buzzer(3) + bz.on() + + :type pin: int or str + :param pin: + The GPIO pin which the buzzer is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param bool active_high: + If :data:`True` (the default), the buzzer will operate normally with + the circuit described above. If :data:`False` you should wire the + cathode to the GPIO pin, and the anode to a 3V3 pin. + + :type initial_value: bool or None + :param initial_value: + If :data:`False` (the default), the buzzer will be silent initially. If + :data:`None`, the buzzer will be left in whatever state the pin is + found in when configured for output (warning: this can be on). If + :data:`True`, the buzzer will be switched on initially. + + :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). + """ + pass + +Buzzer.beep = Buzzer.blink + + +class PWMOutputDevice(OutputDevice): + """ + Generic output device configured for pulse-width modulation (PWM). + + :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. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the GPIO + to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to + LOW (the :meth:`off` method always does the opposite). + + :param float initial_value: + If 0 (the default), the device's duty cycle will be 0 initially. + Other values between 0 and 1 can be specified as an initial duty cycle. + Note that :data:`None` cannot be specified (unlike the parent class) as + there is no way to tell PWM not to alter the state of the pin. + + :param int frequency: + The frequency (in Hz) of pulses emitted to drive the device. Defaults + to 100Hz. + + :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, pin=None, *, active_high=True, initial_value=0, + frequency=100, pin_factory=None): + self._blink_thread = None + self._controller = None + if not 0 <= initial_value <= 1: + raise OutputDeviceBadValue("initial_value must be between 0 and 1") + super().__init__(pin, active_high=active_high, initial_value=None, + pin_factory=pin_factory) + try: + # XXX need a way of setting these together + self.pin.frequency = frequency + self.value = initial_value + except: + self.close() + raise + + def close(self): + try: + self._stop_blink() + except AttributeError: + pass + try: + self.pin.frequency = None + except AttributeError: + # If the pin's already None, ignore the exception + pass + super().close() + + def _state_to_value(self, state): + return float(state if self.active_high else 1 - state) + + def _value_to_state(self, value): + return float(value if self.active_high else 1 - value) + + def _write(self, value): + if not 0 <= value <= 1: + raise OutputDeviceBadValue("PWM value must be between 0 and 1") + super()._write(value) + + @property + def value(self): + """ + The duty cycle of the PWM device. 0.0 is off, 1.0 is fully on. Values + in between may be specified for varying levels of power in the device. + """ + return super().value + + @value.setter + def value(self, value): + self._stop_blink() + self._write(value) + def on(self): self._stop_blink() - super(LED, self).on() + self._write(1) def off(self): self._stop_blink() - super(LED, self).off() + self._write(0) + def toggle(self): + """ + Toggle the state of the device. If the device is currently off + (:attr:`value` is 0.0), this changes it to "fully" on (:attr:`value` is + 1.0). If the device has a duty cycle (:attr:`value`) of 0.1, this will + toggle it to 0.9, and so on. + """ + self._stop_blink() + self.value = 1 - self.value -class Buzzer(OutputDevice): - pass + @property + def is_active(self): + """ + Returns :data:`True` if the device is currently active (:attr:`value` + is non-zero) and :data:`False` otherwise. + """ + return self.value != 0 + + @property + def frequency(self): + """ + The frequency of the pulses used with the PWM device, in Hz. The + default is 100Hz. + """ + return self.pin.frequency + + @frequency.setter + def frequency(self, value): + self.pin.frequency = value + + def blink( + self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, + n=None, background=True): + """ + Make the device turn on and off repeatedly. + + :param float on_time: + Number of seconds on. Defaults to 1 second. + + :param float off_time: + Number of seconds off. Defaults to 1 second. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 0. + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 0. + + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True` (the default), start a background thread to + continue blinking and return immediately. If :data:`False`, only + return when the blink is finished (warning: the default value of + *n* will result in this method never returning). + """ + self._stop_blink() + self._blink_thread = GPIOThread( + self._blink_device, + (on_time, off_time, fade_in_time, fade_out_time, n) + ) + self._blink_thread.start() + if not background: + self._blink_thread.join() + self._blink_thread = None + + def pulse(self, fade_in_time=1, fade_out_time=1, n=None, background=True): + """ + Make the device fade in and out repeatedly. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 1. + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 1. + + :type n: int or None + :param n: + Number of times to pulse; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True` (the default), start a background thread to + continue pulsing and return immediately. If :data:`False`, only + return when the pulse is finished (warning: the default value of + *n* will result in this method never returning). + """ + on_time = off_time = 0 + self.blink( + on_time, off_time, fade_in_time, fade_out_time, n, background + ) + + def _stop_blink(self): + if self._controller: + self._controller._stop_blink(self) + self._controller = None + if self._blink_thread: + self._blink_thread.stop() + self._blink_thread = None + + def _blink_device( + self, on_time, off_time, fade_in_time, fade_out_time, n, fps=25): + sequence = [] + if fade_in_time > 0: + sequence += [ + (i * (1 / fps) / fade_in_time, 1 / fps) + for i in range(int(fps * fade_in_time)) + ] + sequence.append((1, on_time)) + if fade_out_time > 0: + sequence += [ + (1 - (i * (1 / fps) / fade_out_time), 1 / fps) + for i in range(int(fps * fade_out_time)) + ] + sequence.append((0, off_time)) + sequence = ( + cycle(sequence) if n is None else + chain.from_iterable(repeat(sequence, n)) + ) + for value, delay in sequence: + self._write(value) + if self._blink_thread.stopping.wait(delay): + break + + +class TonalBuzzer(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a tonal buzzer. + + :type pin: int or str + :param pin: + The GPIO pin which the buzzer is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param float initial_value: + If :data:`None` (the default), the buzzer will be off initially. Values + between -1 and 1 can be specified as an initial value for the buzzer. + + :type mid_tone: int or str + :param mid_tone: + The tone which is represented the device's middle value (0). The + default is "A4" (MIDI note 69). + + :param int octaves: + The number of octaves to allow away from the base note. The default is + 1, meaning a value of -1 goes one octave below the base note, and one + above, i.e. from A3 to A5 with the default base note of A4. + + :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). + .. note:: -class Motor(OutputDevice): + Note that this class does not currently work with + :class:`~gpiozero.pins.pigpio.PiGPIOFactory`. + """ + + def __init__(self, pin=None, *, initial_value=None, mid_tone=Tone("A4"), + octaves=1, pin_factory=None): + self._mid_tone = None + super().__init__( + pwm_device=PWMOutputDevice(pin=pin, pin_factory=pin_factory), + pin_factory=pin_factory) + try: + self._mid_tone = Tone(mid_tone) + if not (0 < octaves <= 9): + raise ValueError('octaves must be between 1 and 9') + self._octaves = octaves + try: + self.min_tone.note + except ValueError: + raise ValueError( + f'{self._mid_tone!r} is too low for {self._octaves} ' + f'octaves') + try: + self.max_tone.note + except ValueError: + raise ValueError( + f'{self._mid_tone!r} is too high for {self._octaves} ' + f'octaves') + self.value = initial_value + except: + self.close() + raise + + def __repr__(self): + try: + self._check_open() + if self.value is None: + return ( + f'') + else: + return ( + f'') + except DeviceClosed: + return super().__repr__() + + def play(self, tone): + """ + Play the given *tone*. This can either be an instance of + :class:`~gpiozero.tones.Tone` or can be anything that could be used to + construct an instance of :class:`~gpiozero.tones.Tone`. + + For example:: + + >>> from gpiozero import TonalBuzzer + >>> from gpiozero.tones import Tone + >>> b = TonalBuzzer(17) + >>> b.play(Tone("A4")) + >>> b.play(Tone(220.0)) # Hz + >>> b.play(Tone(60)) # middle C in MIDI notation + >>> b.play("A4") + >>> b.play(220.0) + >>> b.play(60) + """ + if tone is None: + self.value = None + else: + if not isinstance(tone, Tone): + tone = Tone(tone) + freq = tone.frequency + if self.min_tone.frequency <= tone <= self.max_tone.frequency: + self.pwm_device.pin.frequency = freq + self.pwm_device.value = 0.5 + else: + raise ValueError("tone is out of the device's range") + + def stop(self): + """ + Turn the buzzer off. This is equivalent to setting :attr:`value` to + :data:`None`. + """ + self.value = None + + @property + def tone(self): + """ + Returns the :class:`~gpiozero.tones.Tone` that the buzzer is currently + playing, or :data:`None` if the buzzer is silent. This property can + also be set to play the specified tone. + """ + if self.pwm_device.pin.frequency is None: + return None + else: + return Tone.from_frequency(self.pwm_device.pin.frequency) + + @tone.setter + def tone(self, value): + self.play(value) + + @property + def value(self): + """ + Represents the state of the buzzer as a value between -1 (representing + the minimum tone) and 1 (representing the maximum tone). This can also + be the special value :data:`None` indicating that the buzzer is + currently silent. + """ + if self.pwm_device.pin.frequency is None: + return None + else: + # Can't have zero-division here; zero-frequency Tone cannot be + # generated and self.octaves cannot be zero either + return log2( + self.pwm_device.pin.frequency / self.mid_tone.frequency + ) / self.octaves + + @value.setter + def value(self, value): + if value is None: + self.pwm_device.pin.frequency = None + elif -1 <= value <= 1: + freq = self.mid_tone.frequency * 2 ** (self.octaves * value) + self.pwm_device.pin.frequency = freq + self.pwm_device.value = 0.5 + else: + raise OutputDeviceBadValue( + 'TonalBuzzer value must be between -1 and 1, or None') + + @property + def is_active(self): + """ + Returns :data:`True` if the buzzer is currently playing, otherwise + :data:`False`. + """ + return self.value is not None + + @property + def octaves(self): + """ + The number of octaves available (above and below mid_tone). + """ + return self._octaves + + @property + def min_tone(self): + """ + The lowest tone that the buzzer can play, i.e. the tone played + when :attr:`value` is -1. + """ + return self._mid_tone.down(12 * self.octaves) + + @property + def mid_tone(self): + """ + The middle tone available, i.e. the tone played when :attr:`value` is + 0. + """ + return self._mid_tone + + @property + def max_tone(self): + """ + The highest tone that the buzzer can play, i.e. the tone played when + :attr:`value` is 1. + """ + return self._mid_tone.up(12 * self.octaves) + + +class PWMLED(PWMOutputDevice): + """ + Extends :class:`PWMOutputDevice` and represents a light emitting diode + (LED) with variable brightness. + + A typical configuration of such a device is to connect a GPIO pin to the + anode (long leg) of the LED, and the cathode (short leg) to ground, with + an optional resistor to prevent the LED from burning out. + + :type pin: int or str + :param pin: + The GPIO pin which the LED is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the GPIO + to HIGH. If :data:`False`, the :meth:`on` method will set the GPIO to + LOW (the :meth:`off` method always does the opposite). + + :param float initial_value: + If ``0`` (the default), the LED will be off initially. Other values + between 0 and 1 can be specified as an initial brightness for the LED. + Note that :data:`None` cannot be specified (unlike the parent class) as + there is no way to tell PWM not to alter the state of the pin. + + :param int frequency: + The frequency (in Hz) of pulses emitted to drive the LED. Defaults + to 100Hz. + + :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). + """ pass + +PWMLED.is_lit = PWMLED.is_active + + +class RGBLED(SourceMixin, Device): + """ + Extends :class:`Device` and represents a full color LED component (composed + of red, green, and blue LEDs). + + Connect the common cathode (longest leg) to a ground pin; connect each of + the other legs (representing the red, green, and blue anodes) to any GPIO + pins. You should use three limiting resistors (one per anode). + + The following code will make the LED yellow:: + + from gpiozero import RGBLED + + led = RGBLED(2, 3, 4) + led.color = (1, 1, 0) + + The `colorzero`_ library is also supported:: + + from gpiozero import RGBLED + from colorzero import Color + + led = RGBLED(2, 3, 4) + led.color = Color('yellow') + + :type red: int or str + :param red: + The GPIO pin that controls the red component of the RGB LED. See + :ref:`pin-numbering` for valid pin numbers. If this is :data:`None` a + :exc:`GPIODeviceError` will be raised. + + :type green: int or str + :param green: + The GPIO pin that controls the green component of the RGB LED. + + :type blue: int or str + :param blue: + The GPIO pin that controls the blue component of the RGB LED. + + :param bool active_high: + Set to :data:`True` (the default) for common cathode RGB LEDs. If you + are using a common anode RGB LED, set this to :data:`False`. + + :type initial_value: ~colorzero.Color or tuple + :param initial_value: + The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMLED` instances for + each component of the RGBLED. If :data:`False`, construct regular + :class:`LED` instances, which prevents smooth color graduations. + + :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). + + .. _colorzero: https://colorzero.readthedocs.io/ + """ + def __init__(self, red=None, green=None, blue=None, *, active_high=True, + initial_value=(0, 0, 0), pwm=True, pin_factory=None): + self._leds = () + self._blink_thread = None + if not all(p is not None for p in [red, green, blue]): + raise GPIOPinMissing('red, green, and blue pins must be provided') + LEDClass = PWMLED if pwm else LED + super().__init__(pin_factory=pin_factory) + self._leds = tuple( + LEDClass(pin, active_high=active_high, pin_factory=pin_factory) + for pin in (red, green, blue)) + self.value = initial_value + + def close(self): + if getattr(self, '_leds', None): + self._stop_blink() + for led in self._leds: + led.close() + self._leds = () + super().close() + + @property + def closed(self): + return len(self._leds) == 0 + + @property + def value(self): + """ + Represents the color of the LED as an RGB 3-tuple of ``(red, green, + blue)`` where each value is between 0 and 1 if *pwm* was :data:`True` + when the class was constructed (and only 0 or 1 if not). + + For example, red would be ``(1, 0, 0)`` and yellow would be ``(1, 1, + 0)``, while orange would be ``(1, 0.5, 0)``. + """ + return tuple(led.value for led in self._leds) + + @value.setter + def value(self, value): + for component in value: + if not 0 <= component <= 1: + raise OutputDeviceBadValue( + 'each RGB color component must be between 0 and 1') + if isinstance(self._leds[0], LED): + if component not in (0, 1): + raise OutputDeviceBadValue( + 'each RGB color component must be 0 or 1 with non-PWM ' + 'RGBLEDs') + self._stop_blink() + for led, v in zip(self._leds, value): + led.value = v + + @property + def is_active(self): + """ + Returns :data:`True` if the LED is currently active (not black) and + :data:`False` otherwise. + """ + return self.value != (0, 0, 0) + + is_lit = is_active + + @property + def color(self): + """ + Represents the color of the LED as a :class:`~colorzero.Color` object. + """ + return Color(*self.value) + + @color.setter + def color(self, value): + self.value = value + + @property + def red(self): + """ + Represents the red element of the LED as a :class:`~colorzero.Red` + object. + """ + return self.color.red + + @red.setter + def red(self, value): + self._stop_blink() + r, g, b = self.value + self.value = value, g, b + + @property + def green(self): + """ + Represents the green element of the LED as a :class:`~colorzero.Green` + object. + """ + return self.color.green + + @green.setter + def green(self, value): + self._stop_blink() + r, g, b = self.value + self.value = r, value, b + + @property + def blue(self): + """ + Represents the blue element of the LED as a :class:`~colorzero.Blue` + object. + """ + return self.color.blue + + @blue.setter + def blue(self, value): + self._stop_blink() + r, g, b = self.value + self.value = r, g, value + + def on(self): + """ + Turn the LED on. This equivalent to setting the LED color to white + ``(1, 1, 1)``. + """ + self.value = (1, 1, 1) + + def off(self): + """ + Turn the LED off. This is equivalent to setting the LED color to black + ``(0, 0, 0)``. + """ + self.value = (0, 0, 0) + + def toggle(self): + """ + Toggle the state of the device. If the device is currently off + (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on + (:attr:`value` is ``(1, 1, 1)``). If the device has a specific color, + this method inverts the color. + """ + r, g, b = self.value + self.value = (1 - r, 1 - g, 1 - b) + + def blink( + self, on_time=1, off_time=1, fade_in_time=0, fade_out_time=0, + on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): + """ + Make the device turn on and off repeatedly. + + :param float on_time: + Number of seconds on. Defaults to 1 second. + + :param float off_time: + Number of seconds off. Defaults to 1 second. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 0. Must be 0 if + *pwm* was :data:`False` when the class was constructed + (:exc:`ValueError` will be raised if not). + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 0. Must be 0 if + *pwm* was :data:`False` when the class was constructed + (:exc:`ValueError` will be raised if not). + + :type on_color: ~colorzero.Color or tuple + :param on_color: + The color to use when the LED is "on". Defaults to white. + + :type off_color: ~colorzero.Color or tuple + :param off_color: + The color to use when the LED is "off". Defaults to black. + + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True` (the default), start a background thread to + continue blinking and return immediately. If :data:`False`, only + return when the blink is finished (warning: the default value of + *n* will result in this method never returning). + """ + if isinstance(self._leds[0], LED): + if fade_in_time: + raise ValueError('fade_in_time must be 0 with non-PWM RGBLEDs') + if fade_out_time: + raise ValueError('fade_out_time must be 0 with non-PWM RGBLEDs') + self._stop_blink() + self._blink_thread = GPIOThread( + self._blink_device, + ( + on_time, off_time, fade_in_time, fade_out_time, + on_color, off_color, n + ) + ) + self._blink_thread.start() + if not background: + self._blink_thread.join() + self._blink_thread = None + + def pulse( + self, fade_in_time=1, fade_out_time=1, + on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): + """ + Make the device fade in and out repeatedly. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 1. + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 1. + + :type on_color: ~colorzero.Color or tuple + :param on_color: + The color to use when the LED is "on". Defaults to white. + + :type off_color: ~colorzero.Color or tuple + :param off_color: + The color to use when the LED is "off". Defaults to black. + + :type n: int or None + :param n: + Number of times to pulse; :data:`None` (the default) means forever. + + :param bool background: + If :data:`True` (the default), start a background thread to + continue pulsing and return immediately. If :data:`False`, only + return when the pulse is finished (warning: the default value of + *n* will result in this method never returning). + """ + on_time = off_time = 0 + self.blink( + on_time, off_time, fade_in_time, fade_out_time, + on_color, off_color, n, background + ) + + def _stop_blink(self, led=None): + # If this is called with a single led, we stop all blinking anyway + if self._blink_thread: + self._blink_thread.stop() + self._blink_thread = None + + def _blink_device( + self, on_time, off_time, fade_in_time, fade_out_time, on_color, + off_color, n, fps=25): + # Define a simple lambda to perform linear interpolation between + # off_color and on_color + lerp = lambda t, fade_in: tuple( + (1 - t) * off + t * on + if fade_in else + (1 - t) * on + t * off + for off, on in zip(off_color, on_color) + ) + sequence = [] + if fade_in_time > 0: + sequence += [ + (lerp(i * (1 / fps) / fade_in_time, True), 1 / fps) + for i in range(int(fps * fade_in_time)) + ] + sequence.append((on_color, on_time)) + if fade_out_time > 0: + sequence += [ + (lerp(i * (1 / fps) / fade_out_time, False), 1 / fps) + for i in range(int(fps * fade_out_time)) + ] + sequence.append((off_color, off_time)) + sequence = ( + cycle(sequence) if n is None else + chain.from_iterable(repeat(sequence, n)) + ) + for l in self._leds: + l._controller = self + for value, delay in sequence: + for l, v in zip(self._leds, value): + l._write(v) + if self._blink_thread.stopping.wait(delay): + break + + +class Motor(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a generic motor + connected to a bi-directional motor driver circuit (i.e. an `H-bridge`_). + + Attach an `H-bridge`_ motor controller to your Pi; connect a power source + (e.g. a battery pack or the 5V pin) to the controller; connect the outputs + of the controller board to the two terminals of the motor; connect the + inputs of the controller board to two GPIO pins. + + .. _H-bridge: https://en.wikipedia.org/wiki/H_bridge + + The following code will make the motor turn "forwards":: + + from gpiozero import Motor + + motor = Motor(17, 18) + motor.forward() + + :type forward: int or str + :param forward: + The GPIO pin that the forward input of the motor driver chip is + connected to. See :ref:`pin-numbering` for valid pin numbers. If this + is :data:`None` a :exc:`GPIODeviceError` will be raised. + + :type backward: int or str + :param backward: + The GPIO pin that the backward input of the motor driver chip is + connected to. See :ref:`pin-numbering` for valid pin numbers. If this + is :data:`None` a :exc:`GPIODeviceError` will be raised. + + :type enable: int or str or None + :param enable: + The GPIO pin that enables the motor. Required for *some* motor + controller boards. See :ref:`pin-numbering` for valid pin numbers. + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMOutputDevice` + instances for the motor controller pins, allowing both direction and + variable speed control. If :data:`False`, construct + :class:`DigitalOutputDevice` instances, allowing only direction + control. + + :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, forward, backward, *, enable=None, pwm=True, + pin_factory=None): + PinClass = PWMOutputDevice if pwm else DigitalOutputDevice + devices = OrderedDict(( + ('forward_device', PinClass(forward, pin_factory=pin_factory)), + ('backward_device', PinClass(backward, pin_factory=pin_factory)), + )) + if enable is not None: + devices['enable_device'] = DigitalOutputDevice( + enable, + initial_value=True, + pin_factory=pin_factory + ) + super().__init__(_order=devices.keys(), pin_factory=pin_factory, **devices) + + @property + def value(self): + """ + Represents the speed of the motor as a floating point value between -1 + (full speed backward) and 1 (full speed forward), with 0 representing + stopped. + """ + return self.forward_device.value - self.backward_device.value + + @value.setter + def value(self, value): + if not -1 <= value <= 1: + raise OutputDeviceBadValue("Motor value must be between -1 and 1") + if value > 0: + try: + self.forward(value) + except ValueError as e: + raise OutputDeviceBadValue(e) + elif value < 0: + try: + self.backward(-value) + except ValueError as e: + raise OutputDeviceBadValue(e) + else: + self.stop() + + @property + def is_active(self): + """ + Returns :data:`True` if the motor is currently running and + :data:`False` otherwise. + """ + return self.value != 0 + + def forward(self, speed=1): + """ + Drive the motor forwards. + + :param float speed: + The speed at which the motor should turn. Can be any value between + 0 (stopped) and the default 1 (maximum speed) if *pwm* was + :data:`True` when the class was constructed (and only 0 or 1 if + not). + """ + if not 0 <= speed <= 1: + raise ValueError('forward speed must be between 0 and 1') + if isinstance(self.forward_device, DigitalOutputDevice): + if speed not in (0, 1): + raise ValueError( + 'forward speed must be 0 or 1 with non-PWM Motors') + self.backward_device.off() + self.forward_device.value = speed + + def backward(self, speed=1): + """ + Drive the motor backwards. + + :param float speed: + The speed at which the motor should turn. Can be any value between + 0 (stopped) and the default 1 (maximum speed) if *pwm* was + :data:`True` when the class was constructed (and only 0 or 1 if + not). + """ + if not 0 <= speed <= 1: + raise ValueError('backward speed must be between 0 and 1') + if isinstance(self.backward_device, DigitalOutputDevice): + if speed not in (0, 1): + raise ValueError( + 'backward speed must be 0 or 1 with non-PWM Motors') + self.forward_device.off() + self.backward_device.value = speed + + def reverse(self): + """ + Reverse the current direction of the motor. If the motor is currently + idle this does nothing. Otherwise, the motor's direction will be + reversed at the current speed. + """ + self.value = -self.value + + def stop(self): + """ + Stop the motor. + """ + self.forward_device.off() + self.backward_device.off() + + +class PhaseEnableMotor(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a generic motor connected + to a Phase/Enable motor driver circuit; the phase of the driver controls + whether the motor turns forwards or backwards, while enable controls the + speed with PWM. + + The following code will make the motor turn "forwards":: + + from gpiozero import PhaseEnableMotor + motor = PhaseEnableMotor(12, 5) + motor.forward() + + :type phase: int or str + :param phase: + The GPIO pin that the phase (direction) input of the motor driver chip + is connected to. See :ref:`pin-numbering` for valid pin numbers. If + this is :data:`None` a :exc:`GPIODeviceError` will be raised. + + :type enable: int or str + :param enable: + The GPIO pin that the enable (speed) input of the motor driver chip + is connected to. See :ref:`pin-numbering` for valid pin numbers. If + this is :data:`None` a :exc:`GPIODeviceError` will be raised. + + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMOutputDevice` + instances for the motor controller pins, allowing both direction and + variable speed control. If :data:`False`, construct + :class:`DigitalOutputDevice` instances, allowing only direction + control. + + :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, phase, enable, *, pwm=True, pin_factory=None): + PinClass = PWMOutputDevice if pwm else DigitalOutputDevice + super().__init__( + phase_device=DigitalOutputDevice(phase, pin_factory=pin_factory), + enable_device=PinClass(enable, pin_factory=pin_factory), + _order=('phase_device', 'enable_device'), pin_factory=pin_factory) + + @property + def value(self): + """ + Represents the speed of the motor as a floating point value between -1 + (full speed backward) and 1 (full speed forward). + """ + return ( + -self.enable_device.value + if self.phase_device.is_active else + self.enable_device.value + ) + + @value.setter + def value(self, value): + if not -1 <= value <= 1: + raise OutputDeviceBadValue("Motor value must be between -1 and 1") + if value > 0: + self.forward(value) + elif value < 0: + self.backward(-value) + else: + self.stop() + + @property + def is_active(self): + """ + Returns :data:`True` if the motor is currently running and + :data:`False` otherwise. + """ + return self.value != 0 + + def forward(self, speed=1): + """ + Drive the motor forwards. + + :param float speed: + The speed at which the motor should turn. Can be any value between + 0 (stopped) and the default 1 (maximum speed). + """ + if isinstance(self.enable_device, DigitalOutputDevice): + if speed not in (0, 1): + raise ValueError( + 'forward speed must be 0 or 1 with non-PWM Motors') + self.enable_device.off() + self.phase_device.off() + self.enable_device.value = speed + + def backward(self, speed=1): + """ + Drive the motor backwards. + + :param float speed: + The speed at which the motor should turn. Can be any value between + 0 (stopped) and the default 1 (maximum speed). + """ + if isinstance(self.enable_device, DigitalOutputDevice): + if speed not in (0, 1): + raise ValueError( + 'backward speed must be 0 or 1 with non-PWM Motors') + self.enable_device.off() + self.phase_device.on() + self.enable_device.value = speed + + def reverse(self): + """ + Reverse the current direction of the motor. If the motor is currently + idle this does nothing. Otherwise, the motor's direction will be + reversed at the current speed. + """ + self.value = -self.value + + def stop(self): + """ + Stop the motor. + """ + self.enable_device.off() + + +class Servo(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a PWM-controlled servo + motor connected to a GPIO pin. + + Connect a power source (e.g. a battery pack or the 5V pin) to the power + cable of the servo (this is typically colored red); connect the ground + cable of the servo (typically colored black or brown) to the negative of + your battery pack, or a GND pin; connect the final cable (typically colored + white or orange) to the GPIO pin you wish to use for controlling the servo. + + The following code will make the servo move between its minimum, maximum, + and mid-point positions with a pause between each:: + + from gpiozero import Servo + from time import sleep + + servo = Servo(17) + + while True: + servo.min() + sleep(1) + servo.mid() + sleep(1) + servo.max() + sleep(1) + + You can also use the :attr:`value` property to move the servo to a + particular position, on a scale from -1 (min) to 1 (max) where 0 is the + mid-point:: + + from gpiozero import Servo + + servo = Servo(17) + + servo.value = 0.5 + + .. note:: + + To reduce servo jitter, use the pigpio pin driver rather than the default + RPi.GPIO driver (pigpio uses DMA sampling for much more precise edge + timing). See :ref:`changing-pin-factory` for further information. + + :type pin: int or str + :param pin: + The GPIO pin that the servo is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param float initial_value: + If ``0`` (the default), the device's mid-point will be set initially. + Other values between -1 and +1 can be specified as an initial position. + :data:`None` means to start the servo un-controlled (see + :attr:`value`). + + :param float min_pulse_width: + The pulse width corresponding to the servo's minimum position. This + defaults to 1ms. + + :param float max_pulse_width: + The pulse width corresponding to the servo's maximum position. This + defaults to 2ms. + + :param float frame_width: + The length of time between servo control pulses measured in seconds. + This defaults to 20ms which is a common value for servos. + + :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, pin=None, *, initial_value=0.0, min_pulse_width=1/1000, + max_pulse_width=2/1000, frame_width=20/1000, + pin_factory=None): + if min_pulse_width >= max_pulse_width: + raise ValueError('min_pulse_width must be less than max_pulse_width') + if max_pulse_width >= frame_width: + raise ValueError('max_pulse_width must be less than frame_width') + self._frame_width = frame_width + self._min_dc = min_pulse_width / frame_width + self._dc_range = (max_pulse_width - min_pulse_width) / frame_width + self._min_value = -1 + self._value_range = 2 + super().__init__( + pwm_device=PWMOutputDevice( + pin, frequency=int(1 / frame_width), pin_factory=pin_factory), + pin_factory=pin_factory + ) + + if PiGPIOFactory is None or not isinstance(self.pin_factory, PiGPIOFactory): + warnings.warn(PWMSoftwareFallback( + 'To reduce servo jitter, use the pigpio pin factory.' + 'See https://gpiozero.readthedocs.io/en/stable/api_output.html#servo for more info' + )) + + try: + self.value = initial_value + except: + self.close() + raise + + @property + def frame_width(self): + """ + The time between control pulses, measured in seconds. + """ + return self._frame_width + + @property + def min_pulse_width(self): + """ + The control pulse width corresponding to the servo's minimum position, + measured in seconds. + """ + return self._min_dc * self.frame_width + + @property + def max_pulse_width(self): + """ + The control pulse width corresponding to the servo's maximum position, + measured in seconds. + """ + return (self._dc_range * self.frame_width) + self.min_pulse_width + + @property + def pulse_width(self): + """ + Returns the current pulse width controlling the servo. + """ + if self.pwm_device.pin.frequency is None: + return None + else: + return self.pwm_device.pin.state * self.frame_width + + @pulse_width.setter + def pulse_width(self, value): + self.pwm_device.pin.state = value / self.frame_width + + def min(self): + """ + Set the servo to its minimum position. + """ + self.value = -1 + + def mid(self): + """ + Set the servo to its mid-point position. + """ + self.value = 0 + + def max(self): + """ + Set the servo to its maximum position. + """ + self.value = 1 + + def detach(self): + """ + Temporarily disable control of the servo. This is equivalent to + setting :attr:`value` to :data:`None`. + """ + self.value = None + + def _get_value(self): + if self.pwm_device.pin.frequency is None: + return None + else: + return ( + ((self.pwm_device.pin.state - self._min_dc) / self._dc_range) * + self._value_range + self._min_value) + + @property + def value(self): + """ + Represents the position of the servo as a value between -1 (the minimum + position) and +1 (the maximum position). This can also be the special + value :data:`None` indicating that the servo is currently + "uncontrolled", i.e. that no control signal is being sent. Typically + this means the servo's position remains unchanged, but that it can be + moved by hand. + """ + result = self._get_value() + if result is None: + return result + else: + # NOTE: This round() only exists to ensure we don't confuse people + # by returning 2.220446049250313e-16 as the default initial value + # instead of 0. The reason _get_value and _set_value are split + # out is for descendents that require the un-rounded values for + # accuracy + return round(result, 14) + + @value.setter + def value(self, value): + if value is None: + self.pwm_device.pin.frequency = None + elif -1 <= value <= 1: + self.pwm_device.pin.frequency = int(1 / self.frame_width) + self.pwm_device.pin.state = ( + self._min_dc + self._dc_range * + ((value - self._min_value) / self._value_range) + ) + else: + raise OutputDeviceBadValue( + "Servo value must be between -1 and 1, or None") + + @property + def is_active(self): + return self.value is not None + + +class AngularServo(Servo): + """ + Extends :class:`Servo` and represents a rotational PWM-controlled servo + motor which can be set to particular angles (assuming valid minimum and + maximum angles are provided to the constructor). + + Connect a power source (e.g. a battery pack or the 5V pin) to the power + cable of the servo (this is typically colored red); connect the ground + cable of the servo (typically colored black or brown) to the negative of + your battery pack, or a GND pin; connect the final cable (typically colored + white or orange) to the GPIO pin you wish to use for controlling the servo. + + Next, calibrate the angles that the servo can rotate to. In an interactive + Python session, construct a :class:`Servo` instance. The servo should move + to its mid-point by default. Set the servo to its minimum value, and + measure the angle from the mid-point. Set the servo to its maximum value, + and again measure the angle:: + + >>> from gpiozero import Servo + >>> s = Servo(17) + >>> s.min() # measure the angle + >>> s.max() # measure the angle + + You should now be able to construct an :class:`AngularServo` instance + with the correct bounds:: + + >>> from gpiozero import AngularServo + >>> s = AngularServo(17, min_angle=-42, max_angle=44) + >>> s.angle = 0.0 + >>> s.angle + 0.0 + >>> s.angle = 15 + >>> s.angle + 15.0 + + .. note:: + + You can set *min_angle* greater than *max_angle* if you wish to reverse + the sense of the angles (e.g. ``min_angle=45, max_angle=-45``). This + can be useful with servos that rotate in the opposite direction to your + expectations of minimum and maximum. + + :type pin: int or str + :param pin: + The GPIO pin that the servo is connected to. See :ref:`pin-numbering` + for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError` + will be raised. + + :param float initial_angle: + Sets the servo's initial angle to the specified value. The default is + 0. The value specified must be between *min_angle* and *max_angle* + inclusive. :data:`None` means to start the servo un-controlled (see + :attr:`value`). + + :param float min_angle: + Sets the minimum angle that the servo can rotate to. This defaults to + -90, but should be set to whatever you measure from your servo during + calibration. + + :param float max_angle: + Sets the maximum angle that the servo can rotate to. This defaults to + 90, but should be set to whatever you measure from your servo during + calibration. + + :param float min_pulse_width: + The pulse width corresponding to the servo's minimum position. This + defaults to 1ms. + + :param float max_pulse_width: + The pulse width corresponding to the servo's maximum position. This + defaults to 2ms. + + :param float frame_width: + The length of time between servo control pulses measured in seconds. + This defaults to 20ms which is a common value for servos. + + :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, pin=None, *, initial_angle=0.0, min_angle=-90, + max_angle=90, min_pulse_width=1/1000, max_pulse_width=2/1000, + frame_width=20/1000, pin_factory=None): + self._min_angle = min_angle + self._angular_range = max_angle - min_angle + if initial_angle is None: + initial_value = None + elif ((min_angle <= initial_angle <= max_angle) or + (max_angle <= initial_angle <= min_angle)): + initial_value = 2 * ((initial_angle - min_angle) / self._angular_range) - 1 + else: + raise OutputDeviceBadValue( + f"AngularServo angle must be between {min_angle} and " + f"{max_angle}, or None") + super().__init__(pin, initial_value=initial_value, + min_pulse_width=min_pulse_width, + max_pulse_width=max_pulse_width, + frame_width=frame_width, pin_factory=pin_factory) + + @property + def min_angle(self): + """ + The minimum angle that the servo will rotate to when :meth:`min` is + called. + """ + return self._min_angle + + @property + def max_angle(self): + """ + The maximum angle that the servo will rotate to when :meth:`max` is + called. + """ + return self._min_angle + self._angular_range + + @property + def angle(self): + """ + The position of the servo as an angle measured in degrees. This will + only be accurate if :attr:`min_angle` and :attr:`max_angle` have been + set appropriately in the constructor. + + This can also be the special value :data:`None` indicating that the + servo is currently "uncontrolled", i.e. that no control signal is being + sent. Typically this means the servo's position remains unchanged, but + that it can be moved by hand. + """ + result = self._get_value() + if result is None: + return None + else: + # NOTE: Why round(n, 12) here instead of 14? Angle ranges can be + # much larger than -1..1 so we need a little more rounding to + # smooth off the rough corners! + return round( + self._angular_range * + ((result - self._min_value) / self._value_range) + + self._min_angle, 12) + + @angle.setter + def angle(self, angle): + if angle is None: + self.value = None + elif ((self.min_angle <= angle <= self.max_angle) or + (self.max_angle <= angle <= self.min_angle)): + self.value = ( + self._value_range * + ((angle - self._min_angle) / self._angular_range) + + self._min_value) + else: + raise OutputDeviceBadValue( + f"AngularServo angle must be between {self.min_angle} and " + f"{self.max_angle}, or None") diff --git a/gpiozero/pins/__init__.py b/gpiozero/pins/__init__.py new file mode 100644 index 000000000..0fbc407b4 --- /dev/null +++ b/gpiozero/pins/__init__.py @@ -0,0 +1,1418 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2015-2023 Dave Jones +# Copyright (c) 2018 Rick Ansell +# Copyright (c) 2018 Mike Kazantsev +# Copyright (c) 2016 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import warnings +from weakref import ref +from threading import Lock +from textwrap import dedent +from itertools import cycle +from operator import attrgetter +from collections import defaultdict, namedtuple + +from .style import Style +from ..devices import Device +from ..exc import ( + PinInvalidPin, + PinInvalidFunction, + PinSetInput, + PinFixedPull, + PinUnsupported, + PinSPIUnsupported, + PinPWMUnsupported, + PinEdgeDetectUnsupported, + PinMultiplePins, + PinNoPins, + SPIFixedClockMode, + SPIFixedBitOrder, + SPIFixedSelect, + SPIFixedWordSize, + SPIFixedRate, + GPIOPinInUse, +) + + +class Factory: + """ + Generates pins and SPI interfaces for devices. This is an abstract + base class for pin factories. Descendents *must* override the following + methods: + + * :meth:`ticks` + * :meth:`ticks_diff` + * :meth:`_get_board_info` + + Descendents *may* override the following methods, if applicable: + + * :meth:`close` + * :meth:`reserve_pins` + * :meth:`release_pins` + * :meth:`release_all` + * :meth:`pin` + * :meth:`spi` + """ + def __init__(self): + self._reservations = defaultdict(list) + self._res_lock = Lock() + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + + def reserve_pins(self, requester, *names): + """ + Called to indicate that the device reserves the right to use the + specified pin *names*. This should be done during device construction. + If pins are reserved, you must ensure that the reservation is released + by eventually called :meth:`release_pins`. + """ + with self._res_lock: + pins = ( + info + for name in names + for header, info in self.board_info.find_pin(name) + ) + for pin in pins: + for reserver_ref in self._reservations[pin]: + reserver = reserver_ref() + if reserver is not None and requester._conflicts_with(reserver): + raise GPIOPinInUse( + f'pin {pin.name} is already in use by {reserver!r}') + self._reservations[pin].append(ref(requester)) + + def release_pins(self, reserver, *names): + """ + Releases the reservation of *reserver* against pin *names*. This is + typically called during :meth:`~gpiozero.Device.close` to clean up + reservations taken during construction. Releasing a reservation that is + not currently held will be silently ignored (to permit clean-up after + failed / partial construction). + """ + pins = ( + info + for name in names + for header, info in self.board_info.find_pin(name) + ) + with self._res_lock: + for pin in pins: + self._reservations[pin] = [ + ref for ref in self._reservations[pin] + if ref() not in (reserver, None) # may as well clean up dead refs + ] + + def release_all(self, reserver): + """ + Releases all pin reservations taken out by *reserver*. See + :meth:`release_pins` for further information). + """ + # Yes, this would be more efficient if it simply regenerated the + # reservations list without any references to reserver instead of + # (in release_pins) looping over each pin individually. However, this + # then causes a subtle bug in LocalPiFactory which does something + # horribly naughty (with good reason) and makes its _reservations + # dictionary equivalent to a class-level one. + self.release_pins(reserver, *( + pin.name for pin in self._reservations)) + + def close(self): + """ + Closes the pin factory. This is expected to clean up all resources + manipulated by the factory. It it typically called at script + termination. + """ + pass + + def pin(self, name): + """ + Creates an instance of a :class:`Pin` descendent representing the + specified pin. + + .. warning:: + + Descendents must ensure that pin instances representing the same + hardware are identical; i.e. two separate invocations of + :meth:`pin` for the same pin specification must return the same + object. + """ + raise PinUnsupported( # pragma: no cover + "Individual pins are not supported by this pin factory") + + def spi(self, **spi_args): + """ + Returns an instance of an :class:`SPI` interface, for the specified SPI + *port* and *device*, or for the specified pins (*clock_pin*, + *mosi_pin*, *miso_pin*, and *select_pin*). Only one of the schemes can + be used; attempting to mix *port* and *device* with pin numbers will + raise :exc:`SPIBadArgs`. + """ + raise PinSPIUnsupported( # pragma: no cover + 'SPI not supported by this pin factory') + + def ticks(self): + """ + Return the current ticks, according to the factory. The reference point + is undefined and thus the result of this method is only meaningful when + compared to another value returned by this method. + + The format of the time is also arbitrary, as is whether the time wraps + after a certain duration. Ticks should only be compared using the + :meth:`ticks_diff` method. + """ + raise NotImplementedError + + def ticks_diff(self, later, earlier): + """ + Return the time in seconds between two :meth:`ticks` results. The + arguments are specified in the same order as they would be in the + formula *later* - *earlier* but the result is guaranteed to be in + seconds, and to be positive even if the ticks "wrapped" between calls + to :meth:`ticks`. + """ + raise NotImplementedError + + def _get_board_info(self): + raise NotImplementedError + + board_info = property( + lambda self: self._get_board_info(), + doc="""\ + Returns a :class:`BoardInfo` instance (or derivative) representing the + board that instances generated by this factory will be attached to. + """) + + def _get_pi_info(self): + warnings.warn( + DeprecationWarning( + "Please use Factory.board_info instead of Factory.pi_info")) + return self._get_board_info() + + pi_info = property(lambda self: self._get_pi_info()) + + +class Pin: + """ + Abstract base class representing a pin attached to some form of controller, + be it GPIO, SPI, ADC, etc. + + Descendents should override property getters and setters to accurately + represent the capabilities of pins. Descendents *must* override the + following methods: + + * :meth:`_get_info` + * :meth:`_get_function` + * :meth:`_set_function` + * :meth:`_get_state` + + Descendents *may* additionally override the following methods, if + applicable: + + * :meth:`close` + * :meth:`output_with_state` + * :meth:`input_with_pull` + * :meth:`_set_state` + * :meth:`_get_frequency` + * :meth:`_set_frequency` + * :meth:`_get_pull` + * :meth:`_set_pull` + * :meth:`_get_bounce` + * :meth:`_set_bounce` + * :meth:`_get_edges` + * :meth:`_set_edges` + * :meth:`_get_when_changed` + * :meth:`_set_when_changed` + """ + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + self.close() + + def __repr__(self): + return "" # pragma: no cover + + def close(self): + """ + Cleans up the resources allocated to the pin. After this method is + called, this :class:`Pin` instance may no longer be used to query or + control the pin's state. + """ + pass + + def output_with_state(self, state): + """ + Sets the pin's function to "output" and specifies an initial state + for the pin. By default this is equivalent to performing:: + + pin.function = 'output' + pin.state = state + + However, descendents may override this in order to provide the smallest + possible delay between configuring the pin for output and specifying an + initial value (which can be important for avoiding "blips" in + active-low configurations). + """ + self.function = 'output' + self.state = state + + def input_with_pull(self, pull): + """ + Sets the pin's function to "input" and specifies an initial pull-up + for the pin. By default this is equivalent to performing:: + + pin.function = 'input' + pin.pull = pull + + However, descendents may override this in order to provide the smallest + possible delay between configuring the pin for input and pulling the + pin up/down (which can be important for avoiding "blips" in some + configurations). + """ + self.function = 'input' + self.pull = pull + + def _get_info(self): + raise NotImplementedError + + info = property( + lambda self: self._get_info(), + doc="""\ + Returns the :class:`PinInfo` associated with the pin. This can be used + to determine physical properties of the pin, including its location on + the header, fixed pulls, and the various specs that can be used to + identify it. + """) + + def _get_function(self): + raise NotImplementedError + + def _set_function(self, value): + raise NotImplementedError + + function = property( + lambda self: self._get_function(), + lambda self, value: self._set_function(value), + doc="""\ + The function of the pin. This property is a string indicating the + current function or purpose of the pin. Typically this is the string + "input" or "output". However, in some circumstances it can be other + strings indicating non-GPIO related functionality. + + With certain pin types (e.g. GPIO pins), this attribute can be changed + to configure the function of a pin. If an invalid function is + specified, for this attribute, :exc:`PinInvalidFunction` will be + raised. + """) + + def _get_state(self): + raise NotImplementedError + + def _set_state(self, value): + raise PinSetInput(f"Cannot set the state of pin {self!r}") # pragma: no cover + + state = property( + lambda self: self._get_state(), + lambda self, value: self._set_state(value), + doc="""\ + The state of the pin. This is 0 for low, and 1 for high. As a low level + view of the pin, no swapping is performed in the case of pull ups (see + :attr:`pull` for more information): + + .. code-block:: text + + HIGH - - - - > ,---------------------- + | + | + LOW ----------------' + + Descendents which implement analog, or analog-like capabilities can + return values between 0 and 1. For example, pins implementing PWM + (where :attr:`frequency` is not :data:`None`) return a value between + 0.0 and 1.0 representing the current PWM duty cycle. + + If a pin is currently configured for input, and an attempt is made to + set this attribute, :exc:`PinSetInput` will be raised. If an invalid + value is specified for this attribute, :exc:`PinInvalidState` will be + raised. + """) + + def _get_pull(self): + return 'floating' # pragma: no cover + + def _set_pull(self, value): + raise PinFixedPull( # pragma: no cover + f"Cannot change pull-up on pin {self!r}") + + pull = property( + lambda self: self._get_pull(), + lambda self, value: self._set_pull(value), + doc="""\ + The pull-up state of the pin represented as a string. This is typically + one of the strings "up", "down", or "floating" but additional values + may be supported by the underlying hardware. + + If the pin does not support changing pull-up state (for example because + of a fixed pull-up resistor), attempts to set this property will raise + :exc:`PinFixedPull`. If the specified value is not supported by the + underlying hardware, :exc:`PinInvalidPull` is raised. + """) + + def _get_frequency(self): + return None # pragma: no cover + + def _set_frequency(self, value): + if value is not None: + raise PinPWMUnsupported( # pragma: no cover + f"PWM is not supported on pin {self!r}") + + frequency = property( + lambda self: self._get_frequency(), + lambda self, value: self._set_frequency(value), + doc="""\ + The frequency (in Hz) for the pin's PWM implementation, or :data:`None` + if PWM is not currently in use. This value always defaults to + :data:`None` and may be changed with certain pin types to activate or + deactivate PWM. + + If the pin does not support PWM, :exc:`PinPWMUnsupported` will be + raised when attempting to set this to a value other than :data:`None`. + """) + + def _get_bounce(self): + return None # pragma: no cover + + def _set_bounce(self, value): + if value is not None: # pragma: no cover + raise PinEdgeDetectUnsupported( + f"Edge detection is not supported on pin {self!r}") + + bounce = property( + lambda self: self._get_bounce(), + lambda self, value: self._set_bounce(value), + doc="""\ + The amount of bounce detection (elimination) currently in use by edge + detection, measured in seconds. If bounce detection is not currently in + use, this is :data:`None`. + + For example, if :attr:`edges` is currently "rising", :attr:`bounce` is + currently 5/1000 (5ms), then the waveform below will only fire + :attr:`when_changed` on two occasions despite there being three rising + edges: + + .. code-block:: text + + TIME 0...1...2...3...4...5...6...7...8...9...10..11..12 ms + + bounce elimination |===================| |============== + + HIGH - - - - > ,--. ,--------------. ,--. + | | | | | | + | | | | | | + LOW ----------------' `-' `-' `----------- + : : + : : + when_changed when_changed + fires fires + + If the pin does not support edge detection, attempts to set this + property will raise :exc:`PinEdgeDetectUnsupported`. If the pin + supports edge detection, the class must implement bounce detection, + even if only in software. + """) + + def _get_edges(self): + return 'none' # pragma: no cover + + def _set_edges(self, value): + raise PinEdgeDetectUnsupported( # pragma: no cover + f"Edge detection is not supported on pin {self!r}") + + edges = property( + lambda self: self._get_edges(), + lambda self, value: self._set_edges(value), + doc="""\ + The edge that will trigger execution of the function or bound method + assigned to :attr:`when_changed`. This can be one of the strings + "both" (the default), "rising", "falling", or "none": + + .. code-block:: text + + HIGH - - - - > ,--------------. + | | + | | + LOW --------------------' `-------------- + : : + : : + Fires when_changed "both" "both" + when edges is ... "rising" "falling" + + If the pin does not support edge detection, attempts to set this + property will raise :exc:`PinEdgeDetectUnsupported`. + """) + + def _get_when_changed(self): + return None # pragma: no cover + + def _set_when_changed(self, value): + raise PinEdgeDetectUnsupported( # pragma: no cover + f"Edge detection is not supported on pin {self!r}") + + when_changed = property( + lambda self: self._get_when_changed(), + lambda self, value: self._set_when_changed(value), + doc="""\ + A function or bound method to be called when the pin's state changes + (more specifically when the edge specified by :attr:`edges` is detected + on the pin). The function or bound method must accept two parameters: + the first will report the ticks (from :meth:`Factory.ticks`) when + the pin's state changed, and the second will report the pin's current + state. + + .. warning:: + + Depending on hardware support, the state is *not guaranteed to be + accurate*. For instance, many GPIO implementations will provide + an interrupt indicating when a pin's state changed but not what it + changed to. In this case the pin driver simply reads the pin's + current state to supply this parameter, but the pin's state may + have changed *since* the interrupt. Exercise appropriate caution + when relying upon this parameter. + + If the pin does not support edge detection, attempts to set this + property will raise :exc:`PinEdgeDetectUnsupported`. + """) + + +class SPI(Device): + """ + Abstract interface for `Serial Peripheral Interface`_ (SPI) + implementations. Descendents *must* override the following methods: + + * :meth:`transfer` + * :meth:`_get_clock_mode` + + Descendents *may* override the following methods, if applicable: + + * :meth:`read` + * :meth:`write` + * :meth:`_set_clock_mode` + * :meth:`_get_lsb_first` + * :meth:`_set_lsb_first` + * :meth:`_get_select_high` + * :meth:`_set_select_high` + * :meth:`_get_bits_per_word` + * :meth:`_set_bits_per_word` + + .. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus + """ + + def read(self, n): + """ + Read *n* words of data from the SPI interface, returning them as a + sequence of unsigned ints, each no larger than the configured + :attr:`bits_per_word` of the interface. + + This method is typically used with read-only devices that feature + half-duplex communication. See :meth:`transfer` for full duplex + communication. + """ + return self.transfer([0] * n) + + def write(self, data): + """ + Write *data* to the SPI interface. *data* must be a sequence of + unsigned integer words each of which will fit within the configured + :attr:`bits_per_word` of the interface. The method returns the number + of words written to the interface (which may be less than or equal to + the length of *data*). + + This method is typically used with write-only devices that feature + half-duplex communication. See :meth:`transfer` for full duplex + communication. + """ + return len(self.transfer(data)) + + def transfer(self, data): + """ + Write *data* to the SPI interface. *data* must be a sequence of + unsigned integer words each of which will fit within the configured + :attr:`bits_per_word` of the interface. The method returns the sequence + of words read from the interface while writing occurred (full duplex + communication). + + The length of the sequence returned dictates the number of words of + *data* written to the interface. Each word in the returned sequence + will be an unsigned integer no larger than the configured + :attr:`bits_per_word` of the interface. + """ + raise NotImplementedError + + @property + def clock_polarity(self): + """ + The polarity of the SPI clock pin. If this is :data:`False` (the + default), the clock pin will idle low, and pulse high. Setting this to + :data:`True` will cause the clock pin to idle high, and pulse low. On + many data sheets this is documented as the CPOL value. + + The following diagram illustrates the waveform when + :attr:`clock_polarity` is :data:`False` (the default), equivalent to + CPOL 0: + + .. code-block:: text + + on on on on on on on + ,---. ,---. ,---. ,---. ,---. ,---. ,---. + CLK | | | | | | | | | | | | | | + | | | | | | | | | | | | | | + ------' `---' `---' `---' `---' `---' `---' `------ + idle off off off off off off idle + + The following diagram illustrates the waveform when + :attr:`clock_polarity` is :data:`True`, equivalent to CPOL 1: + + .. code-block:: text + + idle off off off off off off idle + ------. ,---. ,---. ,---. ,---. ,---. ,---. ,------ + | | | | | | | | | | | | | | + CLK | | | | | | | | | | | | | | + `---' `---' `---' `---' `---' `---' `---' + on on on on on on on + """ + return bool(self.clock_mode & 2) + + @clock_polarity.setter + def clock_polarity(self, value): + self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1) + + @property + def clock_phase(self): + """ + The phase of the SPI clock pin. If this is :data:`False` (the default), + data will be read from the MISO pin when the clock pin activates. + Setting this to :data:`True` will cause data to be read from the MISO + pin when the clock pin deactivates. On many data sheets this is + documented as the CPHA value. Whether the clock edge is rising or + falling when the clock is considered activated is controlled by the + :attr:`clock_polarity` attribute (corresponding to CPOL). + + The following diagram indicates when data is read when + :attr:`clock_polarity` is :data:`False`, and :attr:`clock_phase` is + :data:`False` (the default), equivalent to CPHA 0: + + .. code-block:: text + + ,---. ,---. ,---. ,---. ,---. ,---. ,---. + CLK | | | | | | | | | | | | | | + | | | | | | | | | | | | | | + ----' `---' `---' `---' `---' `---' `---' `------- + : : : : : : : + MISO---. ,---. ,---. ,---. ,---. ,---. ,---. + / \\ / \\ / \\ / \\ / \\ / \\ / \\ + -{ Bit X Bit X Bit X Bit X Bit X Bit X Bit }------ + \\ / \\ / \\ / \\ / \\ / \\ / \\ / + `---' `---' `---' `---' `---' `---' `---' + + The following diagram indicates when data is read when + :attr:`clock_polarity` is :data:`False`, but :attr:`clock_phase` is + :data:`True`, equivalent to CPHA 1: + + .. code-block:: text + + ,---. ,---. ,---. ,---. ,---. ,---. ,---. + CLK | | | | | | | | | | | | | | + | | | | | | | | | | | | | | + ----' `---' `---' `---' `---' `---' `---' `------- + : : : : : : : + MISO ,---. ,---. ,---. ,---. ,---. ,---. ,---. + / \\ / \\ / \\ / \\ / \\ / \\ / \\ + -----{ Bit X Bit X Bit X Bit X Bit X Bit X Bit }-- + \\ / \\ / \\ / \\ / \\ / \\ / \\ / + `---' `---' `---' `---' `---' `---' `---' + """ + return bool(self.clock_mode & 1) + + @clock_phase.setter + def clock_phase(self, value): + self.clock_mode = self.clock_mode & (~1) | bool(value) + + def _get_clock_mode(self): + raise NotImplementedError # pragma: no cover + + def _set_clock_mode(self, value): + raise SPIFixedClockMode( # pragma: no cover + f"clock_mode cannot be changed on {self!r}") + + clock_mode = property( + lambda self: self._get_clock_mode(), + lambda self, value: self._set_clock_mode(value), + doc="""\ + Presents a value representing the :attr:`clock_polarity` and + :attr:`clock_phase` attributes combined according to the following + table: + + +------+-----------------+--------------+ + | mode | polarity (CPOL) | phase (CPHA) | + +======+=================+==============+ + | 0 | False | False | + +------+-----------------+--------------+ + | 1 | False | True | + +------+-----------------+--------------+ + | 2 | True | False | + +------+-----------------+--------------+ + | 3 | True | True | + +------+-----------------+--------------+ + + Adjusting this value adjusts both the :attr:`clock_polarity` and + :attr:`clock_phase` attributes simultaneously. + """) + + def _get_lsb_first(self): + return False # pragma: no cover + + def _set_lsb_first(self, value): + raise SPIFixedBitOrder( # pragma: no cover + f"lsb_first cannot be changed on {self!r}") + + lsb_first = property( + lambda self: self._get_lsb_first(), + lambda self, value: self._set_lsb_first(value), + doc="""\ + Controls whether words are read and written LSB in (Least Significant + Bit first) order. The default is :data:`False` indicating that words + are read and written in MSB (Most Significant Bit first) order. + Effectively, this controls the `Bit endianness`_ of the connection. + + The following diagram shows the a word containing the number 5 (binary + 0101) transmitted on MISO with :attr:`bits_per_word` set to 4, and + :attr:`clock_mode` set to 0, when :attr:`lsb_first` is :data:`False` + (the default): + + .. code-block:: text + + ,---. ,---. ,---. ,---. + CLK | | | | | | | | + | | | | | | | | + ----' `---' `---' `---' `----- + : ,-------. : ,-------. + MISO: | : | : | : | + : | : | : | : | + ----------' : `-------' : `---- + : : : : + MSB LSB + + And now with :attr:`lsb_first` set to :data:`True` (and all other + parameters the same): + + .. code-block:: text + + ,---. ,---. ,---. ,---. + CLK | | | | | | | | + | | | | | | | | + ----' `---' `---' `---' `----- + ,-------. : ,-------. : + MISO: | : | : | : + | : | : | : | : + --' : `-------' : `----------- + : : : : + LSB MSB + + .. _Bit endianness: https://en.wikipedia.org/wiki/Endianness#Bit_endianness + """) + + def _get_select_high(self): + return False # pragma: no cover + + def _set_select_high(self, value): + raise SPIFixedSelect( # pragma: no cover + f"select_high cannot be changed on {self!r}") + + select_high = property( + lambda self: self._get_select_high(), + lambda self, value: self._set_select_high(value), + doc="""\ + If :data:`False` (the default), the chip select line is considered + active when it is pulled low. When set to :data:`True`, the chip select + line is considered active when it is driven high. + + The following diagram shows the waveform of the chip select line, and + the clock when :attr:`clock_polarity` is :data:`False`, and + :attr:`select_high` is :data:`False` (the default): + + .. code-block:: text + + ---. ,------ + __ | | + CS | chip is selected, and will react to clock | idle + `-----------------------------------------------------' + + ,---. ,---. ,---. ,---. ,---. ,---. ,---. + CLK | | | | | | | | | | | | | | + | | | | | | | | | | | | | | + ----' `---' `---' `---' `---' `---' `---' `------- + + And when :attr:`select_high` is :data:`True`: + + .. code-block:: text + + ,-----------------------------------------------------. + CS | chip is selected, and will react to clock | idle + | | + ---' `------ + + ,---. ,---. ,---. ,---. ,---. ,---. ,---. + CLK | | | | | | | | | | | | | | + | | | | | | | | | | | | | | + ----' `---' `---' `---' `---' `---' `---' `------- + """) + + def _get_bits_per_word(self): + return 8 # pragma: no cover + + def _set_bits_per_word(self, value): + raise SPIFixedWordSize( # pragma: no cover + f"bits_per_word cannot be changed on {self!r}") + + bits_per_word = property( + lambda self: self._get_bits_per_word(), + lambda self, value: self._set_bits_per_word(value), + doc="""\ + Controls the number of bits that make up a word, and thus where the + word boundaries appear in the data stream, and the maximum value of a + word. Defaults to 8 meaning that words are effectively bytes. + + Several implementations do not support non-byte-sized words. + """) + + def _get_rate(self): + return 100000 # pragma: no cover + + def _set_rate(self, value): + raise SPIFixedRate( # pragma: no cover + f"rate cannot be changed on {self!r}") + + rate = property( + lambda self: self._get_rate(), + lambda self, value: self._set_rate(value), + doc="""\ + Controls the speed of the SPI interface in Hz (or baud). + + Note that most software SPI implementations ignore this property, and + will raise :exc:`SPIFixedRate` if an attempt is made to set it, as they + have no rate control (they simply bit-bang as fast as possible because + typically this isn't very fast anyway, and introducing measures to + limit the rate would simply slow them down to the point of being + useless). + """) + + +class PinInfo(namedtuple('PinInfo', ( + 'number', + 'name', + 'names', + 'pull', + 'row', + 'col', + 'interfaces', + ))): + """ + This class is a :func:`~collections.namedtuple` derivative used to + represent information about a pin present on a GPIO header. The following + attributes are defined: + + .. attribute:: number + + An integer containing the physical pin number on the header (starting + from 1 in accordance with convention). + + .. attribute:: name + + A string describing the function of the pin. Some common examples + include "GND" (for pins connecting to ground), "3V3" (for pins which + output 3.3 volts), "GPIO9" (for GPIO9 in the board's numbering scheme), + etc. + + .. attribute:: names + + A set of all the names that can be used to identify this pin with + :meth:`BoardInfo.find_pin`. The :attr:`name` attribute is the "typical" + name for this pin, and will be one of the values in this set. + + When "gpio" is in :attr:`interfaces`, these names can be used with + :meth:`Factory.pin` to construct a :class:`Pin` instance representing + this pin. + + .. attribute:: pull + + A string indicating the fixed pull of the pin, if any. This is a blank + string if the pin has no fixed pull, but may be "up" in the case of + pins typically used for I2C such as GPIO2 and GPIO3 on a Raspberry Pi. + + .. attribute:: row + + An integer indicating on which row the pin is physically located in + the header (1-based) + + .. attribute:: col + + An integer indicating in which column the pin is physically located + in the header (1-based) + + .. attribute:: interfaces + + A :class:`dict` mapping interfaces that this pin can be a part of to + the description of that pin in that interface (e.g. "i2c" might map to + "I2C0 SDA"). Typical keys are "gpio", "spi", "i2c", "uart", "pwm", + "smi", and "dpi". + + .. autoattribute:: pull_up + + .. autoattribute:: function + """ + __slots__ = () # workaround python issue #24931 + + @property + def function(self): + """ + Deprecated alias of :attr:`name`. + """ + warnings.warn( + DeprecationWarning( + "PinInfo.function is deprecated; please use PinInfo.name")) + return self.name + + @property + def pull_up(self): + """ + Deprecated variant of :attr:`pull`. + """ + warnings.warn( + DeprecationWarning( + "PinInfo.pull_up is deprecated; please use PinInfo.pull")) + return self.pull == 'up' + + +class HeaderInfo(namedtuple('HeaderInfo', ( + 'name', + 'rows', + 'columns', + 'pins', + ))): + """ + This class is a :func:`~collections.namedtuple` derivative used to + represent information about a pin header on a board. The object can be used + in a format string with various custom specifications:: + + from gpiozero.pins.native import NativeFactory + + factory = NativeFactory() + j8 = factory.board_info.headers['J8'] + print(f'{j8}') + print(f'{j8:full}') + p1 = factory.board_info.headers['P1'] + print(f'{p1:col2}') + print(f'{p1:row1}') + + "color" and "mono" can be prefixed to format specifications to force the + use of `ANSI color codes`_. If neither is specified, ANSI codes will only + be used if stdout is detected to be a tty. "rev" can be added to output the + row or column in reverse order:: + + # force use of ANSI codes + j8 = factory.board_info.headers['J8'] + print(f'{j8:color row2}') + # force plain ASCII + print(f'{j8:mono row2}') + # output in reverse order + print(f'{j8:color rev row1}') + + The following attributes are defined: + + .. automethod:: pprint + + .. attribute:: name + + The name of the header, typically as it appears silk-screened on the + board (e.g. "P1" or "J8"). + + .. attribute:: rows + + The number of rows on the header. + + .. attribute:: columns + + The number of columns on the header. + + .. attribute:: pins + + A dictionary mapping physical pin numbers to :class:`PinInfo` tuples. + + .. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code + """ + __slots__ = () # workaround python issue #24931 + + def _pin_style(self, pin, style): + if 'gpio' in pin.interfaces: + return style('bold green') + elif pin.name == '5V': + return style('bold red') + elif pin.name in {'3V3', '1V8'}: + return style('bold cyan') + elif pin.name in {'GND', 'NC'}: + return style('bold black') + else: + return style('yellow') + + def _format_full(self, style): + Cell = namedtuple('Cell', ('content', 'align', 'style')) + + lines = [] + for row in range(self.rows): + line = [] + for col in range(self.columns): + pin = (row * self.columns) + col + 1 + try: + pin = self.pins[pin] + cells = [ + Cell(pin.name, '><'[col % 2], self._pin_style(pin, style)), + Cell(f'({pin.number})', '><'[col % 2], ''), + ] + if col % 2: + cells = reversed(cells) + line.extend(cells) + except KeyError: + line.append(Cell('', '<', '')) + lines.append(line) + cols = list(zip(*lines)) + col_lens = [max(len(cell.content) for cell in col) for col in cols] + lines = [ + ' '.join( + f'{cell.style}{cell.content:{cell.align}{width}s}{style:reset}' + for cell, width, align in zip(line, col_lens, cycle('><'))) + for line in lines + ] + return '\n'.join(lines) + + def _format_pin(self, pin, style): + return ''.join(( + style('on black'), + ( + ' ' if pin is None else + self._pin_style(pin, style) + + ('1' if pin.number == 1 else + '2' if pin.number == 2 and self.rows == self.columns else + 'o') + ), + style('reset') + )) + + def _format_row(self, row, style, rev=False): + step = -1 if rev else 1 + if row > self.rows: + raise ValueError(f'invalid row {row} for header {self.name}') + start_pin = (row - 1) * self.columns + 1 + return ''.join( + self._format_pin(pin, style) + for n in range(start_pin, start_pin + self.columns)[::step] + for pin in (self.pins.get(n),) + ) + + def _format_col(self, col, style, rev=False): + step = -1 if rev else 1 + if col > self.columns: + raise ValueError(f'invalid col {col} for header {self.name}') + return ''.join( + self._format_pin(pin, style) + for n in range(col, self.rows * self.columns + 1, self.columns)[::step] + for pin in (self.pins.get(n),) + ) + + def __format__(self, format_spec): + style, content = Style.from_style_content(format_spec) + content = set(content.split()) + try: + content.remove('rev') + rev = True + except KeyError: + rev = False + if len(content) != 1: + raise ValueError('Invalid format specifier') + content = content.pop() + if content == 'full': + return self._format_full(style) + elif content.startswith('row') and content[3:].isdigit(): + return self._format_row(int(content[3:]), style, rev=rev) + elif content.startswith('col') and content[3:].isdigit(): + return self._format_col(int(content[3:]), style, rev=rev) + else: + raise ValueError('Invalid format specifier') + + def pprint(self, color=None): + """ + Pretty-print a diagram of the header pins. + + If *color* is :data:`None` (the default, the diagram will include ANSI + color codes if stdout is a color-capable terminal). Otherwise *color* + can be set to :data:`True` or :data:`False` to force color or + monochrome output. + """ + style = Style(color) + print(f'{self:{style} full}') + + +class BoardInfo(namedtuple('BoardInfo', ( + 'revision', + 'model', + 'pcb_revision', + 'released', + 'soc', + 'manufacturer', + 'memory', + 'storage', + 'usb', + 'usb3', + 'ethernet', + 'eth_speed', + 'wifi', + 'bluetooth', + 'csi', + 'dsi', + 'headers', + 'board', + ))): + """ + This class is a :func:`~collections.namedtuple` derivative used to + represent information about a particular board. While it is a tuple, it is + strongly recommended that you use the following named attributes to access + the data contained within. The object can be used in format strings with + various custom format specifications:: + + from gpiozero.pins.native import NativeFactory + + factory = NativeFactory() + print(f'{factory.board_info}' + print(f'{factory.board_info:full}' + print(f'{factory.board_info:board}' + print(f'{factory.board_info:specs}' + print(f'{factory.board_info:headers}' + + "color" and "mono" can be prefixed to format specifications to force the + use of `ANSI color codes`_. If neither is specified, ANSI codes will only + be used if stdout is detected to be a tty:: + + # force use of ANSI codes + print(f'{factory.board_info:color board}') + # force plain ASCII + print(f'{factory.board_info:mono board}') + + .. _ANSI color codes: https://en.wikipedia.org/wiki/ANSI_escape_code + + .. automethod:: physical_pin + + .. automethod:: physical_pins + + .. automethod:: pprint + + .. automethod:: pulled_up + + .. automethod:: to_gpio + + .. attribute:: revision + + A string indicating the revision of the board. This is unique to each + revision and can be considered the "key" from which all other + attributes are derived. However, in itself the string is fairly + meaningless. + + .. attribute:: model + + A string containing the model of the board (for example, "B", "B+", + "A+", "2B", "CM", "Zero", etc.) + + .. attribute:: pcb_revision + + A string containing the PCB revision number which is silk-screened onto + the board. + + .. attribute:: released + + A string containing an approximate release date for this board + (formatted as yyyyQq, e.g. 2012Q1 means the first quarter of 2012). + + .. attribute:: soc + + A string indicating the SoC (`system on a chip`_) that powers this + board. + + .. attribute:: manufacturer + + A string indicating the name of the manufacturer (e.g. "Sony"). + + .. attribute:: memory + + An integer indicating the amount of memory (in Mb) connected to the + SoC. + + .. note:: + + This can differ substantially from the amount of RAM available to + the operating system as the GPU's memory is typically shared with + the CPU. + + .. attribute:: storage + + A string indicating the typical bootable storage used with this board, + e.g. "SD", "MicroSD", or "eMMC". + + .. attribute:: usb + + An integer indicating how many USB ports are physically present on + this board, of any type. + + .. note:: + + This does *not* include any (typically micro-USB) port used to + power the board. + + .. attribute:: usb3 + + An integer indicating how many of the USB ports are USB3 ports on this + board. + + .. attribute:: ethernet + + An integer indicating how many Ethernet ports are physically present + on this board. + + .. attribute:: eth_speed + + An integer indicating the maximum speed (in Mbps) of the Ethernet ports + (if any). If no Ethernet ports are present, this is 0. + + .. attribute:: wifi + + A bool indicating whether this board has wifi built-in. + + .. attribute:: bluetooth + + A bool indicating whether this board has bluetooth built-in. + + .. attribute:: csi + + An integer indicating the number of CSI (camera) ports available on + this board. + + .. attribute:: dsi + + An integer indicating the number of DSI (display) ports available on + this board. + + .. attribute:: headers + + A dictionary which maps header labels to :class:`HeaderInfo` tuples. + For example, to obtain information about header P1 you would query + ``headers['P1']``. To obtain information about pin 12 on header J8 you + would query ``headers['J8'].pins[12]``. + + A rendered version of this data can be obtained by using the + :class:`BoardInfo` object in a format string:: + + from gpiozero.pins.native import NativeFactory + + factory = NativeFactory() + print(f'{factory.board_info:headers}') + + .. attribute:: board + + An ASCII art rendition of the board, primarily intended for console + pretty-print usage. A more usefully rendered version of this data can + be obtained by using the :class:`BoardInfo` object in a format string. + For example:: + + from gpiozero.pins.native import NativeFactory + + factory = NativeFactory() + print(f'{factory.board_info:board}') + + .. autoattribute:: description + + .. _system on a chip: https://en.wikipedia.org/wiki/System_on_a_chip + """ + __slots__ = () # workaround python issue #24931 + + def find_pin(self, name): + """ + A generator function which, given a pin *name*, yields tuples of + :class:`HeaderInfo` and :class:`PinInfo` instances for which *name* + equals :attr:`PinInfo.name`. + """ + for header in self.headers.values(): + for pin in header.pins.values(): + if name in pin.names: + yield header, pin + + def physical_pins(self, function): + """ + Return the physical pins supporting the specified *function* as tuples + of ``(header, pin_number)`` where *header* is a string specifying the + header containing the *pin_number*. Note that the return value is a + :class:`set` which is not indexable. Use :func:`physical_pin` if you + are expecting a single return value. + + :param str function: + The pin function you wish to search for. Usually this is something + like "GPIO9" for GPIO pin 9, or "GND" for all the pins connecting + to electrical ground. + """ + warnings.warn( + DeprecationWarning( + "PiBoardInfo.physical_pins is deprecated; please use " + "BoardInfo.find_pin instead")) + return { + (header.name, pin.number) + for header, pin in self.find_pin(function) + } + + def physical_pin(self, function): + """ + Return the physical pin supporting the specified *function* as a tuple + of ``(header, pin_number)`` where *header* is a string specifying the + header containing the *pin_number*. If no pins support the desired + *function*, this function raises :exc:`PinNoPins`. If multiple pins + support the desired *function*, :exc:`PinMultiplePins` will be raised + (use :func:`physical_pins` if you expect multiple pins in the result, + such as for electrical ground). + + :param str function: + The pin function you wish to search for. Usually this is something + like "GPIO9" for GPIO pin 9. + """ + warnings.warn( + DeprecationWarning( + "PiBoardInfo.physical_pin is deprecated; please use " + "BoardInfo.find_pin instead")) + result = self.physical_pins(function) + if len(result) > 1: + raise PinMultiplePins(f'multiple pins can be used for {function}') + elif result: + return result.pop() + else: + raise PinNoPins(f'no pins can be used for {function}') + + def pulled_up(self, function): + """ + Returns a bool indicating whether a physical pull-up is attached to + the pin supporting the specified *function*. Either :exc:`PinNoPins` + or :exc:`PinMultiplePins` may be raised if the function is not + associated with a single pin. + + :param str function: + The pin function you wish to determine pull-up for. Usually this is + something like "GPIO9" for GPIO pin 9. + """ + warnings.warn( + DeprecationWarning( + "PiBoardInfo.pulled_up is deprecated; please use " + "BoardInfo.find_pin and PinInfo.pull instead")) + for header, pin in self.find_pin(function): + return pin.pull == 'up' + return False + + def to_gpio(self, name): + """ + Parses a pin *name*, returning the primary name of the pin (which can + be used to construct it), or raising a :exc:`ValueError` exception if + the name does not represent a GPIO pin. + + The *name* may be given in any of the following forms: + + * An integer, which will be accepted as a GPIO number + * 'GPIOn' where n is the GPIO number + * 'h:n' where h is the header name and n is the physical pin number + """ + for header, pin in self.find_pin(name): + if 'gpio' in pin.interfaces: + return pin.name + else: + raise PinInvalidPin(f'{name} is not a GPIO pin') + raise PinInvalidPin(f'{name} is not a valid pin name') + + def __repr__(self): + fields=', '.join( + f'{name}=...' if name in ('headers', 'board') else + f'{name}={value!r}' + for name, value in zip(self._fields, self) + ) + return f'{self.__class__.__name__}({fields})' + + def __format__(self, format_spec): + style, content = Style.from_style_content(format_spec) + if content == 'full': + return '\n\n'.join(( + f'{self:{style} specs}', + f'{self:{style} board}', + f'{self:{style} headers}', + )) + elif content == 'board': + kw = self._asdict() + kw.update({ + name: header + for name, header in self.headers.items() + }) + return self.board.format(style=style, **kw) + elif content == 'specs': + if self.memory < 1024: + memory = f'{self.memory}MB' + else: + memory = f'{int(self.memory / 1024)}GB' + return dedent(f"""\ + {style:bold}Description {style:reset}: {self.description} + {style:bold}Revision {style:reset}: {self.revision} + {style:bold}SoC {style:reset}: {self.soc} + {style:bold}RAM {style:reset}: {memory} + {style:bold}Storage {style:reset}: {self.storage} + {style:bold}USB ports {style:reset}: {self.usb} (of which {self.usb3} USB3) + {style:bold}Ethernet ports {style:reset}: {self.ethernet} ({self.eth_speed}Mbps max. speed) + {style:bold}Wi-fi {style:reset}: {self.wifi} + {style:bold}Bluetooth {style:reset}: {self.bluetooth} + {style:bold}Camera ports (CSI) {style:reset}: {self.csi} + {style:bold}Display ports (DSI){style:reset}: {self.dsi}""" + ) + elif content == 'headers': + return '\n\n'.join( + f'{style:bold}{header.name}{style:reset}:\n' + f'{header:{style} full}' + for header in self.headers.values() + ) + else: + raise ValueError('Invalid format specifier') + + def pprint(self, color=None): + """ + Pretty-print a representation of the board along with header diagrams. + + If *color* is :data:`None` (the default), the diagram will include ANSI + color codes if stdout is a color-capable terminal. Otherwise *color* + can be set to :data:`True` or :data:`False` to force color or monochrome + output. + """ + style = Style(color) + print(f'{self:{style} full}') + + @property + def description(self): + """ + A string containing a textual description of the board typically + containing the :attr:`model`, for example "Raspberry Pi 3B" + """ + return f'{self.model} rev {self.pcb_revision}' diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py new file mode 100644 index 000000000..549f4551c --- /dev/null +++ b/gpiozero/pins/data.py @@ -0,0 +1,722 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2019 Ben Nuttall +# Copyright (c) 2017-2018 Andrew Scheller +# +# 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}| {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}|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:7s} 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}""" + +# TODO: Add a CM5_BOARD ascii-art diagram + +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}""" + +# TODO: Add a P500_BOARD ascii-art diagram + +# 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') + +# TODO: The Alt functions on the Pi 5 pins are different to the Pi 4 pins (see the RP1 datasheet), so theoretically we need a load of PI5_GPIO entries (and then update the PI5_J8 header below) + +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_J8 = PI4_J8 # This is slightly wrong, but see the PI5_GPIO comment above + +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.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes + +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, 'RUN': ZERO_RUN}, 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, 'RUN': ZERO_RUN}, APLUS_BOARD, ), + 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'J8': PLUS_J8, 'RUN': ZERO_RUN}, 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, 'RUN': ZERO_RUN}, APLUS_BOARD, ), + } + +SPI_HARDWARE_PINS = { + 0: { + 'clock': 'GPIO11', + 'mosi': 'GPIO10', + 'miso': 'GPIO9', + 'select': ('GPIO8', 'GPIO7'), + }, +} diff --git a/gpiozero/pins/lgpio.py b/gpiozero/pins/lgpio.py new file mode 100644 index 000000000..9fa873942 --- /dev/null +++ b/gpiozero/pins/lgpio.py @@ -0,0 +1,365 @@ +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2021-2023 Dave Jones +# +# 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) diff --git a/gpiozero/pins/local.py b/gpiozero/pins/local.py new file mode 100644 index 000000000..83f5e9537 --- /dev/null +++ b/gpiozero/pins/local.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016 Andrew Scheller +# +# 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) diff --git a/gpiozero/pins/mock.py b/gpiozero/pins/mock.py new file mode 100644 index 000000000..52bd517bd --- /dev/null +++ b/gpiozero/pins/mock.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016 Andrew Scheller +# +# 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) diff --git a/gpiozero/pins/native.py b/gpiozero/pins/native.py new file mode 100644 index 000000000..406b54b05 --- /dev/null +++ b/gpiozero/pins/native.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016-2020 Andrew Scheller +# +# 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) + ) diff --git a/gpiozero/pins/pi.py b/gpiozero/pins/pi.py new file mode 100644 index 000000000..5209b844e --- /dev/null +++ b/gpiozero/pins/pi.py @@ -0,0 +1,727 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2020 Fangchen Li +# +# 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', + 0x15: 'CM4S', + 0x17: '5B', + 0x18: 'CM5', + 0x19: '500', + 0x1a: 'CM5Lite', + }.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', + 'CM4S': '2022Q1', + '400': '2020Q4', + 'Zero2W': '2021Q4', + '5B': '2025Q1' if memory == 16384 else '2023Q4', + 'CM5': '2024Q4', + 'CM5Lite': '2024Q4', + '500': '2024Q4', + }.get(model, 'Unknown') + storage = { + 'A': 'SD', + 'B': 'SD', + 'CM': 'eMMC', + 'CM3': 'eMMC / off-board', + 'CM3+': 'eMMC / off-board', + 'CM4': 'eMMC / off-board', + 'CM4S': 'eMMC / off-board', + 'CM5': 'eMMC', + 'CM5Lite': '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, + 'CM4S': 1, + '400': 3, + 'CM5': 2, + 'CM5Lite': 2, + '500': 3, + }.get(model, 4) + usb3 = { + '4B': 2, + '400': 2, + '5B': 2, + 'CM5': 2, + 'CM5Lite': 2, + '500': 2, + }.get(model, 0) + ethernet = { + 'A': 0, + 'A+': 0, + 'Zero': 0, + 'Zero W': 0, + 'Zero2W': 0, + 'CM': 0, + 'CM3': 0, + '3A+': 0, + 'CM3+': 0, + 'CM4S': 0, + }.get(model, 1) + eth_speed = { + 'B': 100, + 'B+': 100, + '2B': 100, + '3B': 100, + '3B+': 300, + '4B': 1000, + '400': 1000, + 'CM4': 1000, + '5B': 1000, + 'CM5': 1000, + 'CM5Lite': 1000, + '500': 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, + 'CM5': True, + 'CM5Lite': True, + '500': True, + }.get(model, False) + csi = { + 'Zero': 0 if pcb_revision == '1.2' else 1, + 'CM': 2, + 'CM3': 2, + 'CM3+': 2, + '400': 0, + 'CM4': 2, + 'CM4S': 2, + '5B': 2, + 'CM5': 2, + 'CM5Lite': 2, + '500': 0, + }.get(model, 1) + dsi = { + 'Zero': 0, + 'Zero W': 0, + 'Zero2W': 0, + }.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}, + '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}, + '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}, + 'Zero2W': {'J8': data.PLUS_J8}, + '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}, + 'CM4S': {'SODIMM': data.CM3_SODIMM}, + '5B': {'J8': data.PI5_J8, 'J2': data.PI5_J2, 'J7': data.PI5_J7, 'J14': data.PI4_J14}, + 'CM5': {'J8': data.PI5_J8, 'J1': data.CM4_J1, 'J2': data.CM4_J2, 'J3': data.CM4_J3, 'J6': data.CM4_J6, 'J9': data.CM4_J9}, # This is slightly wrong, and will need to be updated when we have a data.CM5_BOARD + 'CM5Lite': {'J8': data.PI5_J8, 'J1': data.CM4_J1, 'J2': data.CM4_J2, 'J3': data.CM4_J3, 'J6': data.CM4_J6, 'J9': data.CM4_J9}, # This is slightly wrong, and will need to be updated when we have a data.CM5_BOARD + '500': {'J8': data.PI5_J8}, + }.get(model, {'J8': data.PLUS_J8, 'RUN': data.ZERO_RUN}) + 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, + 'CM4S': data.CM3PLUS_BOARD, + '400': data.P400_BOARD, + '5B': data.B5_BOARD, + 'CM5': data.CM4_BOARD, # This is slightly wrong, but there isn't a data.CM5_BOARD ascii-art yet + 'CM5Lite': data.CM4_BOARD, # This is slightly wrong, but there isn't a data.CM5_BOARD ascii-art yet + '500': data.P400_BOARD, # This is slightly wrong, but there isn't a data.P500_BOARD ascii-art yet + }.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) diff --git a/gpiozero/pins/pigpio.py b/gpiozero/pins/pigpio.py new file mode 100644 index 000000000..9485e85f8 --- /dev/null +++ b/gpiozero/pins/pigpio.py @@ -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 +# Copyright (c) 2021 Kyle Morgan +# Copyright (c) 2020 Ben Nuttall +# Copyright (c) 2019 Maksim Levental +# Copyright (c) 2019 Aaron Rogers +# Copyright (c) 2016 BuildTools +# Copyright (c) 2016 Andrew Scheller +# +# 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) diff --git a/gpiozero/pins/rpigpio.py b/gpiozero/pins/rpigpio.py new file mode 100644 index 000000000..386fc2231 --- /dev/null +++ b/gpiozero/pins/rpigpio.py @@ -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 +# Copyright (c) 2016 Andrew Scheller +# +# 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) diff --git a/gpiozero/pins/spi.py b/gpiozero/pins/spi.py new file mode 100644 index 000000000..ab8240853 --- /dev/null +++ b/gpiozero/pins/spi.py @@ -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 +# +# 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 diff --git a/gpiozero/pins/style.py b/gpiozero/pins/style.py new file mode 100644 index 000000000..7f48c6b42 --- /dev/null +++ b/gpiozero/pins/style.py @@ -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 +# +# 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) diff --git a/gpiozero/spi_devices.py b/gpiozero/spi_devices.py new file mode 100644 index 000000000..34bfb55ed --- /dev/null +++ b/gpiozero/spi_devices.py @@ -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 +# Copyright (c) 2020 Grzegorz Szymaszek +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016-2019 Andrew Scheller +# Copyright (c) 2016-2018 Ben Nuttall +# +# 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"") + except DeviceClosed: + return f"" + + +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) diff --git a/gpiozero/threads.py b/gpiozero/threads.py new file mode 100644 index 000000000..a45507aff --- /dev/null +++ b/gpiozero/threads.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016 Andrew Scheller +# +# 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) diff --git a/gpiozero/tones.py b/gpiozero/tones.py new file mode 100644 index 000000000..1331ad011 --- /dev/null +++ b/gpiozero/tones.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2019 Ben Nuttall +# +# 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[A-G])' + rf'(?P[{"".join(semitones.keys())}]?)' + rf'(?P[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"" + + @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) diff --git a/gpiozero/tools.py b/gpiozero/tools.py new file mode 100644 index 000000000..dd5db2592 --- /dev/null +++ b/gpiozero/tools.py @@ -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 +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2016-2019 Ben Nuttall +# Copyright (c) 2016 Andrew Scheller +# +# 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]) diff --git a/gpiozerocli/__init__.py b/gpiozerocli/__init__.py new file mode 100644 index 000000000..0f77f623c --- /dev/null +++ b/gpiozerocli/__init__.py @@ -0,0 +1,71 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2021-2024 Dave Jones +# +# SPDX-License-Identifier: BSD-3-Clause + +import os +import sys +import argparse + +# Remove the try clause when 3.7 support is no longer trivial +try: + from importlib_metadata import version +except ImportError: + from importlib.metadata import version + + +class CliTool: + """ + Base class for simple command line utilities. + + The doc-string of the class forms the basis for the utility's help text. + Instances are constructed with a :attr:`parser` which you can customize. + The :meth:`main` method will be called with the parsed command line + arguments and should return an appropriate exit code. + """ + + def __init__(self): + self.parser = argparse.ArgumentParser(description=self.__class__.__doc__) + self.parser.add_argument( + '--version', + action='version', + version=version('gpiozero')) + + def get_formatter(self): + return self.parser._get_formatter() + + def get_gpiozero_help(self): + fmt = self.get_formatter() + fmt.add_text( + """ + Unable to initialize GPIO Zero. This usually means that you are not + running %(prog)s on a Raspberry Pi. If you still wish to run + %(prog)s, set the GPIOZERO_PIN_FACTORY environment variable to + 'mock' and retry, or refer to the Remote GPIO section of the + manual* to configure your environment to remotely access your + Pi. + """) + fmt.add_text( + "* https://gpiozero.readthedocs.io/en/stable/remote_gpio.html") + return fmt.format_help() + + def __call__(self, args=None): + if args is None: + args = sys.argv[1:] + try: + return self.main(self.parser.parse_args(args)) or 0 + except argparse.ArgumentError as e: + # argparse errors are already nicely formatted, print to stderr and + # exit with code 2 + raise e + except Exception as e: + # Output anything else nicely formatted on stderr and exit code 1 + if int(os.environ.get('DEBUG', '0')): + raise + self.parser.exit(1, f'{self.parser.prog}: error: {e}\n') + + def main(self, args): + raise NotImplementedError diff --git a/gpiozerocli/pinout.py b/gpiozerocli/pinout.py new file mode 100755 index 000000000..8ba06fa7d --- /dev/null +++ b/gpiozerocli/pinout.py @@ -0,0 +1,82 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2017-2023 Dave Jones +# Copyright (c) 2017 Ben Nuttall +# +# SPDX-License-Identifier: BSD-3-Clause + +import argparse +import sys +import warnings +import webbrowser + +from . import CliTool +from gpiozero import Device +from gpiozero.pins.pi import PiBoardInfo +from gpiozero.pins.style import Style + + +class PinoutTool(CliTool): + """ + A utility for querying GPIO pin-out information. + """ + def __init__(self): + super().__init__() + self.parser.add_argument( + '-r', '--revision', + dest='revision', + type=lambda s: int(s, base=16), + default=None, + help='Board revision. Default is to autodetect revision of ' + 'current device') + self.parser.add_argument( + '-c', '--color', + action="store_true", + default=None, + help='Force colored output (by default, the output will include ' + 'ANSI color codes if run in a color-capable terminal). See also ' + '--monochrome') + self.parser.add_argument( + '-m', '--monochrome', + dest='color', + action='store_false', + help='Force monochrome output. See also --color') + self.parser.add_argument( + '-x', '--xyz', + dest='xyz', + action='store_true', + help='Open pinout.xyz in the default web browser') + + def main(self, args): + warnings.simplefilter('ignore') + if args.xyz: + webbrowser.open('https://pinout.xyz') + else: + if args.revision is None: + try: + Device.ensure_pin_factory() + board_info = Device.pin_factory.board_info + except ImportError: + sys.stderr.write(self.get_gpiozero_help()) + return 1 + except IOError: + sys.stderr.write('Unrecognized board') + return 1 + else: + board_info = PiBoardInfo.from_revision(args.revision) + style = Style(color=args.color) + sys.stdout.write(f'{board_info:{style} full}') + formatter = self.get_formatter() + formatter.add_text( + "For further information, please refer to " + "https://pinout.xyz/") + sys.stdout.write('\n\n') + sys.stdout.write(formatter.format_help()) + + def output(self, board): + return + + +main = PinoutTool() diff --git a/gpiozerocli/pintest.py b/gpiozerocli/pintest.py new file mode 100644 index 000000000..9f8bb89af --- /dev/null +++ b/gpiozerocli/pintest.py @@ -0,0 +1,154 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2021-2023 Dave Jones +# +# SPDX-License-Identifier: BSD-3-Clause + +import argparse +import sys +import warnings + +from . import CliTool +from gpiozero import Device +from gpiozero.pins.pi import PiBoardInfo + + +class PintestTool(CliTool): + """ + A utility for testing the GPIO pins on a Raspberry Pi, inspired by pigpio's + gpiotest example script, and wiringPi's pintest utility. + """ + def __init__(self): + super().__init__() + self.parser.add_argument( + '-p', '--pins', + dest='pins', default='', + help="The pin(s) to test. Can be specified as a comma-separated " + "list of pins. Pin numbers can be given in any form accepted by " + "gpiozero, e.g. 14, GPIO14, BOARD8. The default is to test all " + "pins") + self.parser.add_argument( + '-s', '--skip', + dest='skip', default='', + help="The pin(s) to skip testing. Can be specified as comma-" + "separated list of pins. Pin numbers can be given in any form " + "accepted by gpiozero, e.g. 14, GPIO14, BOARD8. The default is " + "to skip no pins") + self.parser.add_argument( + '-y', '--yes', + dest='prompt', action='store_false', + help="Proceed without prompting") + self.parser.add_argument( + '-r', '--revision', + dest='revision', type=lambda s: int(s, base=16), default=None, + help="Force board revision. Default is to autodetect revision of " + "current device. You should avoid this option unless you are " + "very sure the detection is incorrect") + + def main(self, args): + if args.revision is None: + try: + Device.ensure_pin_factory() + board_info = Device.pin_factory.board_info + except ImportError: + sys.stderr.write(self.get_gpiozero_help()) + return 1 + except IOError: + sys.stderr.write('Unrecognized board') + return 1 + + pins = self.get_pins( + board_info, + include=args.pins.split(',') if args.pins else (), + exclude=args.skip.split(',') if args.skip else ()) + fmt = self.get_formatter() + fmt.add_text( + f""" + This program checks the board's user-accessible GPIO pins. The + board's model is: {board_info.description}. The following pins are + selected for testing: + """) + fmt.add_text(', '.join(pin.name for pin in pins)) + fmt.add_text( + """ + Please ensure that nothing is connected to any of the pins listed + above for the test duration. + """) + print(fmt.format_help()) + if args.prompt: + s = input('Proceed with test? [y/N] ').strip().lower() + if s != 'y': + return 2 + + failed = [] + for pin in pins: + try: + print(f'Testing {pin.name}...', end='') + self.test_pin(pin) + except ValueError as e: + print(e) + failed.append(pin) + else: + print('ok') + + return 1 if failed else 0 + + def get_pins(self, board, include, exclude): + if not include: + pins = { + pin + for header in board.headers.values() + for pin in header.pins.values() + if 'gpio' in pin.interfaces + } + else: + pins = { + pin + for name in include + for header, pin in board.find_pin(name) + } + skip = { + pin + for name in exclude + for header, pin in board.find_pin(name) + } + pins -= skip + for pin in pins: + if 'gpio' not in pin.interfaces: + raise ValueError(f'{pin.spec} is not a GPIO pin') + return pins + + def test_pin(self, pin_info): + with Device.pin_factory.pin(pin_info.name) as pin: + save_function = pin.function + save_state = pin.state + try: + pin.function = 'output' + pin.state = 0 + if pin.state != 0: + raise ValueError(f'Write 0 to {pin_info.function} failed') + pin.state = 1 + if pin.state != 1: + raise ValueError(f'Write 1 to {pin_info.function} failed') + pin.function = 'input' + if pin_info.pull != 'up': + pin.pull = 'down' + if pin.state != 0: + raise ValueError( + f'Pull down on {pin_info.function} failed') + if pin_info.pull != 'down': + pin.pull = 'up' + if pin.state != 1: + raise ValueError( + f'Pull up on {pin_info.function} failed') + if pin_info.pull == '': + pin.pull = 'floating' + finally: + pin.function = save_function + if save_function == 'output': + pin.state = save_state + + +main = PintestTool() diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index e0e135b59..000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,10 +0,0 @@ -site_name: gpio-zero -theme: readthedocs -site_url: http://pythonhosted.org/gpiozero -repo_url: https://github.com/RPi-Distro/gpio-zero -site_description: A simple interface to everyday GPIO components used with Raspberry Pi -site_author: Ben Nuttall -site_dir: pythonhosted -google_analytics: ['UA-46270871-6', 'pythonhosted.org/gpiozero'] -pages: -- 'Home': 'index.md' diff --git a/rtd_requirements.txt b/rtd_requirements.txt new file mode 100644 index 000000000..fa2610e52 --- /dev/null +++ b/rtd_requirements.txt @@ -0,0 +1,10 @@ +. +Sphinx==8.2.3 +sphinx-rtd-theme==3.0.2 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 diff --git a/scripts/class_graph b/scripts/class_graph new file mode 100755 index 000000000..d122206e8 --- /dev/null +++ b/scripts/class_graph @@ -0,0 +1,229 @@ +#!/usr/bin/python3 + +# SPDX-License-Identifier: BSD-3-Clause + +""" +This script generates Graphviz-compatible dot scripts from the class +definitions of the containing project. Specify the root class to generate with +the -i (multiple roots can be specified). Specify parts of the hierarchy to +exclude with -x. Default configurations can be specified in the containing +project's setup.cfg under [{SETUP_SECTION}] +""" + +from __future__ import annotations + +import re +import sys +assert sys.version_info >= (3, 6), 'Script requires Python 3.6+' +import typing as t +from pathlib import Path +from configparser import ConfigParser +from argparse import ArgumentParser, Namespace, FileType + + +PROJECT_ROOT = (Path(__file__).parent / '..').resolve() +SETUP_SECTION = str(Path(__file__).name) + ':settings' + + +def main(args: t.List[str] = None): + if args is None: + args = sys.argv[1:] + config = get_config(args) + + m = make_class_map(config.source, config.omit) + if config.include or config.exclude: + m = filter_map(m, include_roots=config.include, + exclude_roots=config.exclude) + config.output.write(render_map(m, config.abstract)) + + +def get_config(args: t.List[str]) -> Namespace: + config = ConfigParser( + defaults={ + 'source': '', + 'include': '', + 'exclude': '', + 'abstract': '', + 'omit': '', + 'output': '-', + }, + delimiters=('=',), default_section=SETUP_SECTION, + empty_lines_in_values=False, interpolation=None, + converters={'list': lambda s: s.strip().splitlines()}) + config.read(PROJECT_ROOT / 'setup.cfg') + sect = config[SETUP_SECTION] + # Resolve source and output defaults relative to setup.cfg + if sect['source']: + sect['source'] = '\n'.join( + str(PROJECT_ROOT / source) + for source in sect.getlist('source') + ) + if sect['output'] and sect['output'] != '-': + sect['output'] = str(PROJECT_ROOT / sect['output']) + + parser = ArgumentParser(description=__doc__.format(**globals())) + parser.add_argument( + '-s', '--source', action='append', metavar='PATH', + default=sect.getlist('source'), + help="the pattern(s) of files to search for classes; can be specified " + "multiple times. Default: %(default)r") + parser.add_argument( + '-i', '--include', action='append', metavar='CLASS', + default=sect.getlist('exclude'), + help="only include classes which have BASE somewhere in their " + "ancestry; can be specified multiple times. Default: %(default)r") + parser.add_argument( + '-x', '--exclude', action='append', metavar='CLASS', + default=sect.getlist('exclude'), + help="exclude any classes which have BASE somewhere in their " + "ancestry; can be specified multiple times. Default: %(default)r") + parser.add_argument( + '-o', '--omit', action='append', metavar='CLASS', + default=sect.getlist('omit'), + help="omit the specified class, but not its descendents from the " + "chart; can be specified multiple times. Default: %(default)r") + parser.add_argument( + '-a', '--abstract', action='append', metavar='CLASS', + default=sect.getlist('abstract'), + help="mark the specified class as abstract, rendering it in a " + "different color; can be specified multiple times. Default: " + "%(default)r") + parser.add_argument( + 'output', nargs='?', type=FileType('w'), + default=sect['output'], + help="the file to write the output to; defaults to stdout") + ns = parser.parse_args(args) + ns.abstract = set(ns.abstract) + ns.include = set(ns.include) + ns.exclude = set(ns.exclude) + ns.omit = set(ns.omit) + if not ns.source: + ns.source = [str(PROJECT_ROOT)] + ns.source = set(ns.source) + return ns + + +def make_class_map(search_paths: t.List[str], omit: t.Set[str])\ + -> t.Dict[str, t.Set[str]]: + """ + Find all Python source files under *search_paths*, extract (via a crude + regex) all class definitions and return a mapping of class-name to the list + of base classes. + + All classes listed in *omit* will be excluded from the result, but not + their descendents (useful for excluding "object" etc.) + """ + def find_classes() -> t.Iterator[t.Tuple[str, t.Set[str]]]: + class_re = re.compile( + r'^class\s+(?P\w+)\s*(?:\((?P.*)\))?:', re.MULTILINE) + for path in search_paths: + p = Path(path) + for py_file in p.parent.glob(p.name): + with py_file.open() as f: + for match in class_re.finditer(f.read()): + if match.group('name') not in omit: + yield match.group('name'), { + base.strip() + for base in ( + match.group('bases') or 'object' + ).split(',') + if base.strip() not in omit + } + return { + name: bases + for name, bases in find_classes() + } + + +def filter_map(class_map: t.Dict[str, t.Set[str]], include_roots: t.Set[str], + exclude_roots: t.Set[str]) -> t.Dict[str, t.Set[str]]: + """ + Returns *class_map* (which is a mapping such as that returned by + :func:`make_class_map`), with only those classes which have at least one + of the *include_roots* in their ancestry, and none of the *exclude_roots*. + """ + def has_parent(cls: str, parent: str) -> bool: + return cls == parent or any( + has_parent(base, parent) for base in class_map.get(cls, ())) + + filtered = { + name: bases + for name, bases in class_map.items() + if (not include_roots or + any(has_parent(name, root) for root in include_roots)) + and not any(has_parent(name, root) for root in exclude_roots) + } + pure_bases = { + base for name, bases in filtered.items() for base in bases + } - set(filtered) + # Make a second pass to fill in missing links between classes that are + # only included as bases of other classes + for base in pure_bases: + filtered[base] = pure_bases & class_map[base] + return filtered + + +def render_map(class_map: t.Dict[str, t.Set[str]], abstract: t.Set[str]) -> str: + """ + Renders *class_map* (which is a mapping such as that returned by + :func:`make_class_map`) to graphviz's dot language. + + The *abstract* sequence determines which classes will be rendered lighter + to indicate their abstract nature. All classes with names ending "Mixin" + will be implicitly rendered in a different style. + """ + def all_names(class_map: t.Dict[str, t.Set[str]]) -> t.Iterator[str]: + for name, bases in class_map.items(): + yield name + for base in bases: + yield base + + template = """\ +digraph classes {{ + graph [rankdir=RL]; + node [shape=rect, style=filled, fontname=Sans, fontsize=10]; + edge []; + + /* Mixin classes */ + node [color="#c69ee0", fontcolor="#000000"] + {mixin_nodes} + + /* Abstract classes */ + node [color="#9ec6e0", fontcolor="#000000"] + {abstract_nodes} + + /* Concrete classes */ + node [color="#2980b9", fontcolor="#ffffff"]; + {concrete_nodes} + + /* Edges */ + {edges} +}} +""" + + return template.format( + mixin_nodes='\n '.join( + '{name};'.format(name=name) + for name in sorted(set(all_names(class_map))) + if name.endswith('Mixin') + ), + abstract_nodes='\n '.join( + '{name};'.format(name=name) + for name in sorted(abstract & set(all_names(class_map))) + ), + concrete_nodes='\n '.join( + '{name};'.format(name=name) + for name in sorted(set(all_names(class_map))) + if not name.endswith('Mixin') + and not name in abstract + ), + edges='\n '.join( + '{name}->{base};'.format(name=name, base=base) + for name, bases in sorted(class_map.items()) + for base in sorted(bases) + ), + ) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/copyrights b/scripts/copyrights new file mode 100755 index 000000000..10ce4f486 --- /dev/null +++ b/scripts/copyrights @@ -0,0 +1,403 @@ +#!/usr/bin/python3 + +# SPDX-License-Identifier: BSD-3-Clause + +""" +This script updates the copyright headers on all files project-wide. It derives +the authorship and copyright years information from the git history of the +project; hence, this script must be run within a git clone of the project's +repository. Options are available to specify the license text file, the files +to edit, and files to exclude. Default options can be specified in the +containing project's setup.cfg under [{SETUP_SECTION}] +""" + +from __future__ import annotations + +import os +import sys +assert sys.version_info >= (3, 6), 'Script requires Python 3.6+' +import tempfile +import typing as t +from argparse import ArgumentParser, Namespace +from configparser import ConfigParser +from operator import attrgetter +from itertools import groupby +from datetime import datetime +from subprocess import Popen, PIPE, DEVNULL +from pathlib import Path +from fnmatch import fnmatch + + +PROJECT_ROOT = (Path(__file__).parent / '..').resolve() +SETUP_SECTION = str(Path(__file__).name) + ':settings' +SPDX_PREFIX = 'SPDX-License-Identifier:' +COPYRIGHT_PREFIX = 'Copyright (c)' + + +def main(args: t.List[str] = None): + if args is None: + args = sys.argv[1:] + config = get_config(args) + + writer = CopyWriter.from_config(config) + for path, copyrights in get_copyrights(config.include, config.exclude): + print(f'Re-writing {path}...') + copyrights = sorted( + copyrights, reverse=True, key=lambda c: (max(c.years), c.author)) + with AtomicReplaceFile(path, encoding='utf-8') as target: + with path.open('r') as source: + for chunk in writer.transform(source, copyrights): + target.write(chunk) + + +def get_config(args: t.List[str]) -> Namespace: + config = ConfigParser( + defaults={ + 'include': '**/*', + 'exclude': '', + 'license': 'LICENSE.txt', + 'preamble': '', + 'strip_preamble': 'false', + 'spdx_prefix': SPDX_PREFIX, + 'copy_prefix': COPYRIGHT_PREFIX, + }, + delimiters=('=',), default_section=SETUP_SECTION, + empty_lines_in_values=False, interpolation=None, + converters={'list': lambda s: s.strip().splitlines()}) + config.read(PROJECT_ROOT / 'setup.cfg') + sect = config[SETUP_SECTION] + # Resolve license default relative to setup.cfg + if sect['license']: + sect['license'] = str(PROJECT_ROOT / sect['license']) + + parser = ArgumentParser(description=__doc__.format(**globals())) + parser.add_argument( + '-i', '--include', action='append', metavar='GLOB', + default=sect.getlist('include'), + help="The set of patterns that a file must match to be included in " + "the set of files to re-write. Can be specified multiple times to " + "add several patterns. Default: %(default)r") + parser.add_argument( + '-e', '--exclude', action='append', metavar='GLOB', + default=sect.getlist('exclude'), + help="The set of patterns that a file must *not* match to be included " + "in the set of files to re-write. Can be specified multiple times to " + "add several patterns. Default: %(default)r") + parser.add_argument( + '-l', '--license', action='store', type=Path, metavar='PATH', + default=sect['license'], + help="The file containing the project's license text. If this file " + "contains a SPDX-License-Identifier line (in addition to the license " + "text itself), then matching license text found in source files will " + "be replaced by the SPDX-License-Identifier line (appropriately " + "commented). Default: %(default)s") + parser.add_argument( + '-p', '--preamble', action='append', metavar='STR', + default=sect.getlist('preamble'), + help="The line(s) of text to insert before the copyright attributions " + "in source files. This is typically a brief description of the " + "project. Can be specified multiple times to add several lines. " + "Default: %(default)r") + parser.add_argument( + '-S', '--spdx-prefix', action='store', metavar='STR', + default=sect['spdx_prefix'], + help="The prefix on the line in the license file, and within comments " + "of source files that identifies the appropriate license from the " + "SPDX list. Default: %(default)r") + parser.add_argument( + '-C', '--copy-prefix', action='store', metavar='STR', + default=sect['copy_prefix'], + help="The prefix before copyright attributions in source files. " + "Default: %(default)r") + parser.add_argument( + '--no-strip-preamble', action='store_false', dest='strip_preamble') + parser.add_argument( + '--strip-preamble', action='store_true', + default=sect.getboolean('strip-preamble'), + help="If enabled, any existing preamble matching that specified " + "by --preamble will be removed. This can be used to change the " + "preamble text in files by first specifying the old preamble with " + "this option, then running a second time with the new preamble") + + ns = parser.parse_args(args) + ns.include = set(ns.include) + ns.exclude = set(ns.exclude) + return ns + + +class Copyright(t.NamedTuple): + author: str + email: str + years: t.Set[int] + + def __str__(self): + if len(self.years) > 1: + years = f'{min(self.years)}-{max(self.years)}' + else: + years = f'{min(self.years)}' + return f'{years} {self.author} <{self.email}>' + + +def get_copyrights(include: t.Set[str], exclude: t.Set[str])\ + -> t.Iterator[t.Tuple[Path, t.Iterable[Copyright]]]: + sorted_blame = sorted( + get_contributions(include, exclude), + key=lambda c: (c.path, c.author, c.email) + ) + blame_by_file = { + path: list(file_contributions) + for path, file_contributions in groupby( + sorted_blame, key=attrgetter('path') + ) + } + for path, file_contributors in blame_by_file.items(): + it = groupby(file_contributors, key=lambda c: (c.author, c.email)) + copyrights = [ + Copyright(author, email, {y.year for y in years}) + for (author, email), years in it + ] + yield path, copyrights + + +class Contribution(t.NamedTuple): + author: str + email: str + year: int + path: Path + + +def get_contributions(include: t.Set[str], exclude: t.Set[str])\ + -> t.Iterator[Contribution]: + for path in get_source_paths(include, exclude): + blame = Popen( + ['git', 'blame', '--line-porcelain', 'HEAD', '--', str(path)], + stdout=PIPE, + stderr=PIPE, + universal_newlines=True + ) + author = email = year = None + if blame.stdout is not None: + for line in blame.stdout: + if line.startswith('author '): + author = line.split(' ', 1)[1].rstrip() + elif line.startswith('author-mail '): + email = line.split(' ', 1)[1].rstrip() + email = email.lstrip('<').rstrip('>') + elif line.startswith('author-time '): + # Forget the timezone; we only want the year anyway + timestamp = int(line.split(' ', 1)[1].strip()) + year = datetime.fromtimestamp(timestamp).year + elif line.startswith('filename '): + assert author is not None + assert email is not None + assert year is not None + yield Contribution( + author=author, email=email, year=year, path=path) + author = email = year = None + blame.wait() + assert blame.returncode == 0 + + +def get_source_paths(include: t.Set[str], exclude: t.Set[str])\ + -> t.Iterator[Path]: + ls_tree = Popen( + ['git', 'ls-tree', '-r', '--name-only', 'HEAD'], + stdout=PIPE, stderr=DEVNULL, universal_newlines=True) + if not include: + include = {'*'} + if ls_tree.stdout is not None: + for filename in ls_tree.stdout: + filename = filename.strip() + if any(fnmatch(filename, pattern) for pattern in exclude): + continue + if any(fnmatch(filename, pattern) for pattern in include): + yield Path(filename) + ls_tree.wait() + assert ls_tree.returncode == 0 + + +class License(t.NamedTuple): + ident: t.Optional[str] + text: t.List[str] + + +def get_license(path: Path, *, spdx_prefix: str = SPDX_PREFIX) -> License: + with open(path, 'r') as f: + lines = f.read().splitlines() + + idents = [ + line.rstrip() for line in lines + if line.startswith(spdx_prefix) + ] + ident = None + if len(idents) > 1: + raise RuntimeError(f'More than one {spdx_prefix} line in {path}!') + elif len(idents) == 1: + ident = idents[0] + + body = [ + line.rstrip() for line in lines + if not line.startswith(spdx_prefix) + ] + while not body[0]: + del body[0] + while not body[-1]: + del body[-1] + return License(ident, body) + + +class CopyWriter: + """ + Transformer for the copyright header in source files. The :meth:`transform` + method can be called with a file-like object as the *source* and will + yield chunks of replacement data to be written to the replacement. + """ + + # The script's kinda dumb at this point - only handles straight-forward + # line-based comments, not multi-line delimited styles like /*..*/ + COMMENTS = { + '': '#', + '.c': '//', + '.cpp': '//', + '.js': '//', + '.py': '#', + '.rst': '..', + '.sh': '#', + '.sql': '--', + } + + def __init__(self, license: Path=Path('LICENSE.txt'), + preamble: t.List[str]=None, + spdx_prefix: str=SPDX_PREFIX, + copy_prefix: str=COPYRIGHT_PREFIX): + if preamble is None: + preamble = [] + self.license = get_license(license, spdx_prefix=spdx_prefix) + self.preamble = preamble + self.spdx_prefix = spdx_prefix + self.copy_prefix = copy_prefix + + @classmethod + def from_config(cls, config: Namespace) -> CopyWriter: + return cls( + config.license, config.preamble, + config.spdx_prefix, config.copy_prefix) + + def transform(self, source: t.TextIO, + copyrights: t.List[Copyright], *, + comment_prefix: str=None) -> t.Iterator[str]: + if comment_prefix is None: + comment_prefix = self.COMMENTS[Path(source.name).suffix] + license_start = self.license.text[0] + license_end = self.license.text[-1] + state = 'header' + empty = True + for linenum, line in enumerate(source, start=1): + if state == 'header': + if linenum == 1 and line.startswith('#!'): + yield line + empty = False + elif linenum < 3 and ( + 'fileencoding=' in line or '-*- coding:' in line): + yield line + empty = False + elif line.rstrip() == comment_prefix: + pass # skip blank comment lines + elif line.startswith(f'{comment_prefix} {self.spdx_prefix}'): + pass # skip existing SPDX ident + elif line.startswith(f'{comment_prefix} {self.copy_prefix}'): + pass # skip existing copyright lines + elif any(line.startswith(f'{comment_prefix} {pre_line}') + for pre_line in self.preamble): + pass # skip existing preamble + elif line.startswith(f'{comment_prefix} {license_start}'): + state = 'license' # skip existing license lines + else: + yield from self._generate_header( + copyrights, comment_prefix, empty) + state = 'blank' + elif state == 'license': + if line.startswith(f'{comment_prefix} {license_end}'): + yield from self._generate_header( + copyrights, comment_prefix, empty) + state = 'blank' + continue + if state == 'blank': + # Ensure there's a blank line between license and start of the + # source body + if line.strip(): + yield '\n' + yield line + state = 'body' + elif state == 'body': + yield line + + def _generate_header(self, copyrights: t.Iterable[Copyright], + comment_prefix: str, empty: bool) -> t.Iterator[str]: + if not empty: + yield comment_prefix + '\n' + for line in self.preamble: + yield f'{comment_prefix} {line}\n' + if self.preamble: + yield comment_prefix + '\n' + for copyright in copyrights: + yield f'{comment_prefix} {self.copy_prefix} {copyright!s}\n' + yield comment_prefix + '\n' + if self.license.ident: + yield f'{comment_prefix} {self.license.ident}\n' + else: + for line in self.license.text: + if line: + yield f'{comment_prefix} {line}\n' + else: + yield comment_prefix + '\n' + + +class AtomicReplaceFile: + """ + A context manager for atomically replacing a target file. + + Uses :class:`tempfile.NamedTemporaryFile` to construct a temporary file in + the same directory as the target file. The associated file-like object is + returned as the context manager's variable; you should write the content + you wish to this object. + + When the context manager exits, if no exception has occurred, the temporary + file will be renamed over the target file atomically (after copying + permissions from the target file). If an exception occurs during the + context manager's block, the temporary file will be deleted leaving the + original target file unaffected and the exception will be re-raised. + + :param pathlib.Path path: + The full path and filename of the target file. This is expected to be + an absolute path. + + :param str encoding: + If ``None`` (the default), the temporary file will be opened in binary + mode. Otherwise, this specifies the encoding to use with text mode. + """ + def __init__(self, path: t.Union[str, Path], encoding: str = None): + if isinstance(path, str): + path = Path(path) + self._path = path + self._tempfile = tempfile.NamedTemporaryFile( + mode='wb' if encoding is None else 'w', + dir=str(self._path.parent), encoding=encoding, delete=False) + self._withfile = None + + def __enter__(self): + self._withfile = self._tempfile.__enter__() + return self._withfile + + def __exit__(self, exc_type, exc_value, exc_tb): + os.fchmod(self._withfile.file.fileno(), self._path.stat().st_mode) + result = self._tempfile.__exit__(exc_type, exc_value, exc_tb) + if exc_type is None: + os.rename(self._withfile.name, str(self._path)) + else: + os.unlink(self._withfile.name) + return result + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/previewer b/scripts/previewer new file mode 100755 index 000000000..dbd502e5a --- /dev/null +++ b/scripts/previewer @@ -0,0 +1,211 @@ +#!/usr/bin/python3 + +# SPDX-License-Identifier: BSD-3-Clause + +""" +This script builds the HTML documentation of the containing project, and serves +it from a trivial built-in web-server. It then watches the project source code +for changes, and rebuilds the documentation as necessary. Options are available +to specify the build output directory, the build command, and the paths to +watch for changes. Default options can be specified in the containing project's +setup.cfg under [{SETUP_SECTION}] +""" + +from __future__ import annotations + +import os +import sys +assert sys.version_info >= (3, 6), 'Script requires Python 3.6+' +import time +import shlex +import socket +import traceback +import typing as t +import subprocess as sp +import multiprocessing as mp +from pathlib import Path +from functools import partial +from configparser import ConfigParser +from argparse import ArgumentParser, Namespace +from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler + + +PROJECT_ROOT = Path(__file__).parent / '..' +SETUP_SECTION = str(Path(__file__).name) + ':settings' + + +def main(args: t.List[str] = None): + if args is None: + args = sys.argv[1:] + config = get_config(args) + + queue: mp.Queue = mp.Queue() + builder_proc = mp.Process(target=builder, args=(config, queue), daemon=True) + server_proc = mp.Process(target=server, args=(config, queue), daemon=True) + builder_proc.start() + server_proc.start() + exc, value, tb = queue.get() + server_proc.terminate() + builder_proc.terminate() + traceback.print_exception(exc, value, tb) + + +def get_config(args: t.List[str]) -> Namespace: + config = ConfigParser( + defaults={ + 'command': 'make doc', + 'html': 'build/html', + 'watch': '', + 'ignore': '\n'.join(['*.swp', '*.bak', '*~', '.*']), + 'bind': '0.0.0.0', + 'port': '8000', + }, + delimiters=('=',), default_section=SETUP_SECTION, + empty_lines_in_values=False, interpolation=None, + converters={'list': lambda s: s.strip().splitlines()}) + config.read(PROJECT_ROOT / 'setup.cfg') + sect = config[SETUP_SECTION] + # Resolve html and watch defaults relative to setup.cfg + if sect['html']: + sect['html'] = str(PROJECT_ROOT / sect['html']) + if sect['watch']: + sect['watch'] = '\n'.join( + str(PROJECT_ROOT / watch) + for watch in sect.getlist('watch') + ) + + parser = ArgumentParser(description=__doc__.format(**globals())) + parser.add_argument( + 'html', default=sect['html'], type=Path, nargs='?', + help="The base directory (relative to the project's root) which you " + "wish to server over HTTP. Default: %(default)s") + parser.add_argument( + '-c', '--command', default=sect['command'], + help="The command to run (relative to the project root) to regenerate " + "the HTML documentation. Default: %(default)s") + parser.add_argument( + '-w', '--watch', action='append', default=sect.getlist('watch'), + help="Can be specified multiple times to append to the list of source " + "patterns (relative to the project's root) to watch for changes. " + "Default: %(default)s") + parser.add_argument( + '-i', '--ignore', action='append', default=sect.getlist('ignore'), + help="Can be specified multiple times to append to the list of " + "patterns to ignore. Default: %(default)s") + parser.add_argument( + '--bind', metavar='ADDR', default=sect['bind'], + help="The address to listen on. Default: %(default)s") + parser.add_argument( + '--port', metavar='PORT', default=sect['port'], + help="The port to listen on. Default: %(default)s") + ns = parser.parse_args(args) + ns.command = shlex.split(ns.command) + if not ns.watch: + parser.error('You must specify at least one --watch') + ns.watch = [ + str(Path(watch).relative_to(Path.cwd())) + for watch in ns.watch + ] + return ns + + +class DevRequestHandler(SimpleHTTPRequestHandler): + server_version = 'DocsPreview/1.0' + protocol_version = 'HTTP/1.0' + + +class DevServer(ThreadingHTTPServer): + allow_reuse_address = True + base_path = None + + +def get_best_family(host: t.Union[str, None], port: t.Union[str, int, None])\ + -> t.Tuple[ + socket.AddressFamily, + t.Union[t.Tuple[str, int], t.Tuple[str, int, int, int]] + ]: + infos = socket.getaddrinfo( + host, port, + type=socket.SOCK_STREAM, + flags=socket.AI_PASSIVE) + for family, type, proto, canonname, sockaddr in infos: + return family, sockaddr + + +def server(config: Namespace, queue: mp.Queue = None): + try: + DevServer.address_family, addr = get_best_family(config.bind, config.port) + handler = partial(DevRequestHandler, directory=str(config.html)) + with DevServer(addr[:2], handler) as httpd: + host, port = httpd.socket.getsockname()[:2] + hostname = socket.gethostname() + print(f'Serving {config.html} HTTP on {host} port {port}') + print(f'http://{hostname}:{port}/ ...') + # XXX Wait for queue message to indicate time to start? + httpd.serve_forever() + except: + if queue is not None: + queue.put(sys.exc_info()) + raise + + +def get_stats(config: Namespace) -> t.Dict[Path, os.stat_result]: + return { + path: path.stat() + for watch_pattern in config.watch + for path in Path('.').glob(watch_pattern) + if not any(path.match(ignore_pattern) + for ignore_pattern in config.ignore) + } + + +def get_changes(old_stats: t.Dict[Path, os.stat_result], + new_stats: t.Dict[Path, os.stat_result])\ + -> t.Tuple[t.Set[Path], t.Set[Path], t.Set[Path]]: + # Yes, this is crude and could be more efficient but it's fast enough on a + # Pi so it'll be fast enough on anything else + return ( + new_stats.keys() - old_stats.keys(), # new + old_stats.keys() - new_stats.keys(), # deleted + { # modified + filepath + for filepath in old_stats.keys() & new_stats.keys() + if new_stats[filepath].st_mtime > old_stats[filepath].st_mtime + } + ) + + +def rebuild(config: Namespace) -> t.Dict[Path, os.stat_result]: + print('Rebuilding...') + sp.run(config.command, cwd=PROJECT_ROOT) + return get_stats(config) + + +def builder(config: Namespace, queue: mp.Queue = None): + try: + old_stats = rebuild(config) + print('Watching for changes in:') + print('\n'.join(config.watch)) + # XXX Add some message to the queue to indicate first build done and + # webserver can start? And maybe launch webbrowser too? + while True: + new_stats = get_stats(config) + created, deleted, modified = get_changes(old_stats, new_stats) + if created or deleted or modified: + for filepath in created: + print(f'New file, {filepath}') + for filepath in deleted: + print(f'Deleted file, {filepath}') + for filepath in modified: + print(f'Changed detected in {filepath}') + old_stats = rebuild(config) + else: + time.sleep(0.5) # make sure we're not a busy loop + except: + if queue is not None: + queue.put(sys.exc_info()) + raise + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..890e94609 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,128 @@ +[metadata] +name = gpiozero +version = 2.0.1 +description = A simple interface to GPIO devices with Raspberry Pi +long_description = file:README.rst +author = Ben Nuttall +author_email = ben@bennuttall.com +url = https://gpiozero.readthedocs.io/ +project_urls = + Documentation = https://gpiozero.readthedocs.io/ + Source Code = https://github.com/gpiozero/gpiozero + Issue Tracker = https://github.com/gpiozero/gpiozero/issues +keywords = raspberrypi gpio +license = BSD-3-Clause +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Education + Intended Audience :: Developers + Topic :: Education + Topic :: System :: Hardware + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: Implementation :: PyPy + +[options] +packages = find: +python_requires = >=3.9 +install_requires = + colorzero + importlib_resources~=5.0;python_version<'3.10' + importlib_metadata~=4.6;python_version<'3.10' + +[options.package_data] +gpiozero = + fonts/*.txt + +[options.extras_require] +test = + pytest + pytest-cov +doc = + sphinx>=4.0 + sphinx-rtd-theme>=1.0 + +[options.entry_points] +console_scripts = + pinout = gpiozerocli.pinout:main + pintest = gpiozerocli.pintest:main +gpiozero_pin_factories = + pigpio = gpiozero.pins.pigpio:PiGPIOFactory + lgpio = gpiozero.pins.lgpio:LGPIOFactory + rpigpio = gpiozero.pins.rpigpio:RPiGPIOFactory + native = gpiozero.pins.native:NativeFactory + mock = gpiozero.pins.mock:MockFactory +gpiozero_mock_pin_classes = + mockpin = gpiozero.pins.mock:MockPin + mockpwmpin = gpiozero.pins.mock:MockPWMPin + mockchargingpin = gpiozero.pins.mock:MockChargingPin + mocktriggerpin = gpiozero.pins.mock:MockTriggerPin + +[tool:pytest] +addopts = -rsx --cov --tb=short +testpaths = tests + +[coverage:run] +source = gpiozero +branch = true + +[coverage:report] +ignore_errors = true +show_missing = true +exclude_lines = + pragma: no cover + assert False + raise AssertionError + raise NotImplementedError + pass + if __name__ == .__main__.: + +[copyrights:settings] +include = + **/*.py + **/*.rst +exclude = + docs/examples/*.py + docs/license.rst +license = LICENSE.rst +preamble = + GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +strip-preamble = false + +[class_graph:settings] +abstract = + Device + GPIODevice + SmoothedInputDevice + AnalogInputDevice + MCP3xxx + MCP3xx2 + MCP30xx + MCP32xx + MCP33xx + SPIDevice + CompositeDevice + CompositeOutputDevice + LEDCollection + InternalDevice +omit = + object + GPIOBase + GPIOMeta + frozendict + WeakMethod + _EnergenieMaster +source = + gpiozero/**/*.py + +[previewer:settings] +command = make -C docs html +html = build/html +watch = + gpiozero/**/*.py + gpiozerocli/**/*.py + docs/*.rst + docs/examples/*.py + docs/_static/* diff --git a/setup.py b/setup.py index 71bbca628..606849326 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,3 @@ -import os -from setuptools import setup, find_packages +from setuptools import setup - -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() - - -setup( - name="gpiozero", - version="0.2.0", - author="Ben Nuttall", - description="A simple interface to everyday GPIO components used with Raspberry Pi", - license="BSD", - keywords=[ - "raspberrypi", - "gpio", - ], - url="https://github.com/RPi-Distro/gpio-zero", - packages=find_packages(), - install_requires=[ - "RPi.GPIO", - "w1thermsensor", - ], - long_description=read('README.rst'), - classifiers=[ - "Development Status :: 1 - Planning", - "Intended Audience :: Education", - "Topic :: Education", - "Topic :: System :: Hardware", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 3", - ], -) +setup() diff --git a/tests/cli/test_pinout.py b/tests/cli/test_pinout.py new file mode 100644 index 000000000..af1df9145 --- /dev/null +++ b/tests/cli/test_pinout.py @@ -0,0 +1,39 @@ +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2017-2023 Dave Jones +# Copyright (c) 2016 Stewart +# +# SPDX-License-Identifier: BSD-3-Clause + +import os + +import pytest + +from gpiozerocli.pinout import main + + +def test_args_incorrect(): + with pytest.raises(SystemExit) as ex: + main(['pinout', '--nonexistentarg']) + +def test_args_color(): + args = main.parser.parse_args([]) + assert args.color is None + args = main.parser.parse_args(['--color']) + assert args.color is True + args = main.parser.parse_args(['--monochrome']) + assert args.color is False + +def test_args_revision(): + args = main.parser.parse_args(['--revision', '000d']) + assert args.revision == 13 + +def test_help(capsys): + with pytest.raises(SystemExit) as ex: + main(['pinout', '--help']) + out, err = capsys.readouterr() + assert 'GPIO pin-out' in out + +def test_execution(capsys, no_default_factory): + os.environ['GPIOZERO_PIN_FACTORY'] = 'mock' + assert main([]) == 0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..e6d961efd --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,74 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2019-2023 Dave Jones +# +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import pytest +import warnings +from threading import Event, Thread + +from gpiozero import Device +from gpiozero.pins.mock import MockFactory, MockPWMPin + + +# NOTE: Work-around for python versions <3.4: in these versions the +# resetwarnings function in the warnings module doesn't do much (or doesn't do +# what most would expect). Warnings that have already been triggered still +# won't be re-triggered even after the call. To work-around this we set the +# default filter to "always" on these versions before running any tests. Note +# that this does mean you should run resetwarnings() whenever using +# catch_warnings() +if sys.version_info[:2] < (3, 4): + warnings.simplefilter('always') + +@pytest.fixture() +def no_default_factory(request): + save_pin_factory = Device.pin_factory + Device.pin_factory = None + try: + yield None + finally: + Device.pin_factory = save_pin_factory + +@pytest.fixture(scope='function') +def mock_factory(request): + save_factory = Device.pin_factory + Device.pin_factory = MockFactory() + try: + yield Device.pin_factory + # This reset() may seem redundant given we're re-constructing the + # factory for each function that requires it but MockFactory (via + # LocalFactory) stores some info at the class level which reset() + # clears. + finally: + if Device.pin_factory is not None: + Device.pin_factory.reset() + Device.pin_factory = save_factory + +@pytest.fixture() +def pwm(request, mock_factory): + mock_factory.pin_class = MockPWMPin + +class ThreadedTest(Thread): + def __init__(self, test_fn, *args, **kwargs): + self._fn = test_fn + self._args = args + self._kwargs = kwargs + self._result = None + super().__init__(daemon=True) + self.start() + + def run(self): + self._result = self._fn(*self._args, **self._kwargs) + + @property + def result(self): + self.join(10) + if not self.is_alive(): + return self._result + else: + raise RuntimeError('test thread did not finish') diff --git a/tests/test_boards.py b/tests/test_boards.py new file mode 100644 index 000000000..f937baa40 --- /dev/null +++ b/tests/test_boards.py @@ -0,0 +1,1683 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2020 Jack Wearden +# Copyright (c) 2017-2020 Ben Nuttall +# Copyright (c) 2016-2019 Andrew Scheller +# Copyright (c) 2018 SteveAmor +# Copyright (c) 2018 Claire Pollard +# Copyright (c) 2016 Martin OHanlon +# Copyright (c) 2016 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import pytest +import warnings +from time import sleep +from threading import Event +from unittest import mock + +from gpiozero import * +from gpiozero.fonts import * + + +def test_composite_output_on_off(mock_factory): + pins = [mock_factory.pin(n) for n in (2, 3, 4)] + with CompositeOutputDevice(OutputDevice(2), OutputDevice(3), + foo=OutputDevice(4)) as device: + assert repr(device).startswith('' + assert repr(device2) == '' + assert repr(device3) == '' + assert device1.value + assert not device2.value + assert device3.value is None + assert device1.socket == 1 + assert device2.socket == 2 + assert device3.socket == 3 + [pin.clear_states() for pin in pins] + device1.on() + assert device1.value + pins[0].assert_states_and_times([(0.0, False), (0.0, True)]) + pins[1].assert_states_and_times([(0.0, True), (0.0, True)]) + pins[2].assert_states_and_times([(0.0, True), (0.0, True)]) + pins[3].assert_states_and_times([(0.0, False), (0.0, True)]) + pins[4].assert_states_and_times([(0.0, False)]) + pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) + [pin.clear_states() for pin in pins] + device2.on() + assert device2.value + pins[0].assert_states_and_times([(0.0, True), (0.0, False)]) + pins[1].assert_states_and_times([(0.0, True), (0.0, True)]) + pins[2].assert_states_and_times([(0.0, True), (0.0, True)]) + pins[3].assert_states_and_times([(0.0, True), (0.0, True)]) + pins[4].assert_states_and_times([(0.0, False)]) + pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) + [pin.clear_states() for pin in pins] + device3.on() + assert device3.value + pins[0].assert_states_and_times([(0.0, False), (0.0, True)]) + pins[1].assert_states_and_times([(0.0, True), (0.0, False)]) + pins[2].assert_states_and_times([(0.0, True), (0.0, False)]) + pins[3].assert_states_and_times([(0.0, True), (0.0, False)]) + pins[4].assert_states_and_times([(0.0, False)]) + pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) + + device3.value = False + assert not device3.value + device3.value = True + assert device3.value + with pytest.raises(TypeError): + device3.value = None + + device1.close() + assert repr(device1) == '' + +def test_statuszero_init(mock_factory): + with StatusZero() as sz: + assert repr(sz).startswith(' +# Copyright (c) 2020 Fangchen Li +# +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from gpiozero.compat import frozendict + + +def test_frozendict(): + d = {1: 'a', 2: 'b'} + e = {1: 'a', 2: 'b', 'foo': 'bar'} + f = frozendict(d) + assert f[1] == 'a' + assert f[2] == 'b' + with pytest.raises(KeyError): + f[3] + with pytest.raises(TypeError): + f[3] = 'c' + assert d == f + assert d == f.copy() + assert e == f.copy(foo='bar') + assert len(f) == 2 + assert {k: v for k, v in f.items()} == d + h = hash(f) + assert h is not None + assert hash(f) == h + assert repr(f) == f"" diff --git a/tests/test_devices.py b/tests/test_devices.py new file mode 100644 index 000000000..8c5229822 --- /dev/null +++ b/tests/test_devices.py @@ -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 +# Copyright (c) 2019 Ben Nuttall +# Copyright (c) 2016 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import warnings +import pytest +import errno +from unittest import mock + +from gpiozero import * +from gpiozero.pins.mock import MockFactory + + +file_not_found = IOError(errno.ENOENT, 'File not found') + + +def test_default_pin_factory_order(): + with mock.patch('sys.path') as path, \ + mock.patch('io.open') as io, \ + mock.patch('os.environ.get') as get: + # ensure no pin libraries can be imported + path.return_value = [] + # ensure /proc/device-tree... is not found when trying native + io.return_value.__enter__.side_effect = file_not_found + # ensure pin factory not set in env var + get.return_value = None + with warnings.catch_warnings(record=True) as ws: + warnings.resetwarnings() + with pytest.raises(BadPinFactory): + device = GPIODevice(2) + assert len(ws) == 4 + assert all(w.category == PinFactoryFallback for w in ws) + assert ws[0].message.args[0].startswith('Falling back from lgpio:') + assert ws[1].message.args[0].startswith('Falling back from rpigpio:') + assert ws[2].message.args[0].startswith('Falling back from pigpio:') + assert ws[3].message.args[0].startswith('Falling back from native:') + +def test_device_bad_pin(mock_factory): + with pytest.raises(GPIOPinMissing): + device = GPIODevice() + with pytest.raises(PinInvalidPin): + device = GPIODevice(60) + with pytest.raises(PinInvalidPin): + device = GPIODevice('60') + with pytest.raises(PinInvalidPin): + device = GPIODevice('BCM60') + with pytest.raises(PinInvalidPin): + device = GPIODevice('WPI32') + with pytest.raises(PinInvalidPin): + device = GPIODevice(b'P2:2') + with pytest.raises(PinInvalidPin): + device = GPIODevice('J8:42') + with pytest.raises(PinInvalidPin): + device = GPIODevice('J8:1') + with pytest.raises(PinInvalidPin): + device = GPIODevice('J8:A') + with pytest.raises(PinInvalidPin): + device = GPIODevice('foo') + +def test_device_init(mock_factory): + pin = mock_factory.pin(2) + with GPIODevice(2) as device: + assert repr(device).startswith('' + with pytest.raises(TypeError): + GPIODevice(2, foo='bar') + +def test_device_init_twice_same_pin(mock_factory): + with GPIODevice(2) as device: + with pytest.raises(GPIOPinInUse): + GPIODevice(2) + +def test_device_init_twice_same_pin_different_spec(mock_factory): + with GPIODevice(2) as device: + with pytest.raises(GPIOPinInUse): + GPIODevice("BOARD3") + +def test_device_init_twice_different_pin(mock_factory): + with GPIODevice(2) as device: + with GPIODevice(3) as device2: + pass + +def test_device_close(mock_factory): + device = GPIODevice(2) + # Don't use "with" here; we're testing close explicitly + device.close() + assert device.closed + assert device.pin is None + +def test_device_reopen_same_pin(mock_factory): + pin = mock_factory.pin(2) + with GPIODevice(2) as device: + pass + with GPIODevice(2) as device2: + assert not device2.closed + assert device2.pin is pin + assert device.closed + assert device.pin is None + +def test_device_pin_parsing(mock_factory): + # MockFactory defaults to a Pi 3B layout + pin = mock_factory.pin(2) + with GPIODevice('2') as device: + assert device.pin is pin + with GPIODevice('GPIO2') as device: + assert device.pin is pin + with GPIODevice('BCM2') as device: + assert device.pin is pin + with GPIODevice('WPI8') as device: + assert device.pin is pin + with GPIODevice('BOARD3') as device: + assert device.pin is pin + with GPIODevice('J8:3') as device: + assert device.pin is pin + +def test_device_repr(mock_factory): + with GPIODevice(4) as device: + assert repr(device) == ( + f'') + +def test_device_repr_after_close(mock_factory): + with GPIODevice(2) as device: + pass + assert repr(device) == '' + +def test_device_unknown_attr(mock_factory): + with GPIODevice(2) as device: + with pytest.raises(AttributeError): + device.foo = 1 + +def test_device_broken_attr(mock_factory): + with GPIODevice(2) as device: + del device._active_state + with pytest.raises(AttributeError): + device.value + +def test_device_context_manager(mock_factory): + with GPIODevice(2) as device: + assert not device.closed + assert device.closed + +def test_composite_device_sequence(mock_factory): + with CompositeDevice(InputDevice(4), InputDevice(5)) as device: + assert repr(device).startswith('' + assert device.value == (0, 0) + assert not device.is_active + device[0].pin.drive_high() + assert device.value == (1, 0) + assert device.is_active + +def test_composite_device_named(mock_factory): + with CompositeDevice( + foo=InputDevice(4), + bar=InputDevice(5), + _order=('foo', 'bar') + ) as device: + assert repr(device) == '' + assert device.namedtuple._fields == ('foo', 'bar') + assert device.value == (0, 0) + assert not device.is_active + +def test_composite_device_some_named(mock_factory): + with CompositeDevice( + InputDevice(4), + foobar=InputDevice(5), + ) as device: + assert repr(device) == '' + assert device.namedtuple._fields == ('device_0', 'foobar') + assert device.value == (0, 0) + assert not device.is_active + +def test_composite_device_bad_init(mock_factory): + with pytest.raises(ValueError): + CompositeDevice(foo=1, bar=2, _order=('foo',)) + with pytest.raises(ValueError): + CompositeDevice(close=1) + with pytest.raises(ValueError): + CompositeDevice(2) + with pytest.raises(ValueError): + CompositeDevice(mock_factory.pin(2)) + +def test_composite_device_read_only(mock_factory): + with CompositeDevice(foo=InputDevice(4), bar=InputDevice(5)) as device: + with pytest.raises(AttributeError): + device.foo = 1 + +@pytest.mark.filterwarnings('ignore::gpiozero.exc.PWMSoftwareFallback') +def test_shutdown(mock_factory): + from gpiozero.devices import _shutdown + ds = DistanceSensor(17, 19) + f = Device.pin_factory + _shutdown() + assert ds.closed + assert not f.pins + assert Device.pin_factory is None + # Shutdown must be idempotent + _shutdown() diff --git a/tests/test_fonts.py b/tests/test_fonts.py new file mode 100644 index 000000000..c87ecc86c --- /dev/null +++ b/tests/test_fonts.py @@ -0,0 +1,86 @@ +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2021 Dave Jones +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, +) +str = type('') + +import io + +import pytest + +from gpiozero.fonts import * + + +@pytest.fixture() +def font_text(request): + return """\ +# A comment + + . 0_ 1. 2_ 3_ 4. 5_ 6_ 7_ 8_ 9_ +... |.| ..| ._| ._| |_| |_. |_. ..| |_| |_| +... |_| ..| |_. ._| ..| ._| |_| ..| |_| ._| +""" + + +@pytest.fixture() +def font_data(request): + return { + ' ': (0, 0, 0, 0, 0, 0, 0), + '0': (1, 1, 1, 1, 1, 1, 0), + '1': (0, 1, 1, 0, 0, 0, 0), + '2': (1, 1, 0, 1, 1, 0, 1), + '3': (1, 1, 1, 1, 0, 0, 1), + '4': (0, 1, 1, 0, 0, 1, 1), + '5': (1, 0, 1, 1, 0, 1, 1), + '6': (1, 0, 1, 1, 1, 1, 1), + '7': (1, 1, 1, 0, 0, 0, 0), + '8': (1, 1, 1, 1, 1, 1, 1), + '9': (1, 1, 1, 1, 0, 1, 1), + } + + +def test_fonts_load_types(tmpdir, font_text, font_data): + text_stream = io.StringIO(font_text) + bin_stream = io.BytesIO(font_text.encode('utf-8')) + tmpfile = tmpdir.join('test.font') + tmpfile.write_text(font_text, 'utf-8') + + assert load_font_7seg(text_stream) == font_data + assert load_font_7seg(bin_stream) == font_data + assert load_font_7seg(str(tmpfile)) == font_data + assert load_font_7seg(str(tmpfile).encode('utf-8')) == font_data + + +def test_fonts_bad_data(): + data = """\ + . 0_ 1. 2_ 3_ 4. 5_ 6_ 7_ 8_ 9_ +... |.| ..| ._| ._| |_| |_. |_. ..| |_| |_| +""" + with pytest.raises(ValueError): + load_font_7seg(io.StringIO(data)) + + data = """\ + . 0_ 1 2_ 3_ 4. 5_ 6_ 7_ 8_ 9_ +... |.| .| ._| ._| |_| |_. |_. ..| |_| |_| +... |_| .| |_. ._| ..| ._| |_| ..| |_| ._| +""" + with pytest.raises(ValueError): + load_font_7seg(io.StringIO(data)) + + data = """\ + .... 0--- 2--- 2--- 3--- 4 5--- 6--- 7---. 8--- 9--- +..... | /| | | | | | | | / | | | | +..... | / | --- --- -- ---| --- |--- | --- ---| +..... |/ | | | | | | | | | | | | +..... --- --- --- --- --- --- --- +""" + with pytest.raises(ValueError): + load_font_14seg(io.StringIO(data)) diff --git a/tests/test_inputs.py b/tests/test_inputs.py new file mode 100644 index 000000000..807ff4ae3 --- /dev/null +++ b/tests/test_inputs.py @@ -0,0 +1,502 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2019 Ben Nuttall +# Copyright (c) 2016-2019 Andrew Scheller +# Copyright (c) 2018 Philippe Muller +# +# SPDX-License-Identifier: BSD-3-Clause + +import sys +import pytest +import warnings +from time import sleep +from threading import Event +from functools import partial +from unittest import mock + +from conftest import ThreadedTest +from gpiozero.pins.mock import MockChargingPin, MockTriggerPin +from gpiozero.threads import GPIOThread +from gpiozero import * + + + +def test_input_initial_values(mock_factory): + pin = mock_factory.pin(4) + with InputDevice(4, pull_up=True) as device: + assert repr(device).startswith('' + with InputDevice(4, pull_up=False) as device: + assert pin.pull == 'down' + assert not device.pull_up + +def test_input_is_active_low(mock_factory): + pin = mock_factory.pin(2) + with InputDevice(2, pull_up=True) as device: + pin.drive_high() + assert not device.is_active + assert repr(device) == '' + pin.drive_low() + assert device.is_active + assert repr(device) == '' + +def test_input_is_active_high(mock_factory): + pin = mock_factory.pin(4) + with InputDevice(4, pull_up=False) as device: + pin.drive_high() + assert device.is_active + assert repr(device) == '' + pin.drive_low() + assert not device.is_active + assert repr(device) == '' + +def test_input_pulled_up(mock_factory): + pin = mock_factory.pin(2) + with pytest.raises(PinFixedPull): + InputDevice(2, pull_up=False) + +def test_input_is_active_low_externally_pulled_up(mock_factory): + pin = mock_factory.pin(4) + device = InputDevice(4, pull_up=None, active_state=False) + pin.drive_high() + assert repr(device) == '' + assert not device.is_active + pin.drive_low() + assert repr(device) == '' + assert device.is_active + +def test_input_is_active_high_externally_pulled_down(mock_factory): + pin = mock_factory.pin(4) + device = InputDevice(4, pull_up=None, active_state=True) + pin.drive_high() + assert repr(device) == '' + assert device.is_active + pin.drive_low() + assert repr(device) == '' + assert not device.is_active + +def test_input_invalid_pull_up(mock_factory): + with pytest.raises(PinInvalidState) as exc: + InputDevice(4, pull_up=None) + assert str(exc.value) == 'Pin GPIO4 is defined as floating, but "active_state" is not defined' + +def test_input_invalid_active_state(mock_factory): + with pytest.raises(PinInvalidState) as exc: + InputDevice(4, active_state=True) + assert str(exc.value) == 'Pin GPIO4 is not floating, but "active_state" is not None' + +def test_input_event_activated(mock_factory): + event = Event() + pin = mock_factory.pin(4) + with DigitalInputDevice(4) as device: + assert repr(device).startswith('' + assert device.threshold == 0.5 + assert device.queue_len == 5 + assert not device.partial + device._queue.start() + assert not device.is_active + assert repr(device) == '' + with pytest.raises(InputDeviceError): + device.threshold = 1 + assert repr(device) == '' + with pytest.raises(BadQueueLen): + SmoothedInputDevice(4, queue_len=-1) + with pytest.raises(BadWaitTime): + SmoothedInputDevice(4, sample_wait=-1) + +def test_input_smoothed_values(mock_factory): + pin = mock_factory.pin(4) + with SmoothedInputDevice(4) as device: + device._queue.start() + assert not device.is_active + pin.drive_high() + assert device.wait_for_active(1) + pin.drive_low() + assert device.wait_for_inactive(1) + +def test_input_button(mock_factory): + pin = mock_factory.pin(2) + with Button(2) as button: + assert repr(button).startswith('= 0.0 + pin.drive_high() + evt.clear() + assert button.held_time is None + assert not button.is_held + button.hold_repeat = True + pin.drive_low() + assert evt.wait(1) + evt.clear() + assert evt.wait(1) + pin.drive_high() + evt.clear() + assert not evt.wait(0.1) + +def test_input_line_sensor(mock_factory): + pin = mock_factory.pin(4) + with LineSensor(4) as sensor: + assert repr(sensor).startswith('' + +def test_input_rotary_encoder_jiggle(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21) as encoder: + # Check the FSM permits "jiggle" in the sequence + a_pin.drive_low() + a_pin.drive_high() + a_pin.drive_low() + b_pin.drive_low() + b_pin.drive_high() + b_pin.drive_low() + a_pin.drive_high() + a_pin.drive_low() + a_pin.drive_high() + b_pin.drive_high() + assert encoder.steps == 1 + +def test_input_rotary_encoder_limits(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21, max_steps=4) as encoder: + assert not encoder.wrap + for expected in [1, 2, 3, 4, 4, 4]: + rotate_cw(a_pin, b_pin) + assert encoder.steps == expected + +def test_input_rotary_encoder_threshold(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21, max_steps=4, threshold_steps=(2, 4)) as encoder: + assert encoder.threshold_steps == (2, 4) + for expected in [1, 2, 3, 4]: + rotate_cw(a_pin, b_pin) + assert encoder.is_active == (2 <= encoder.steps <= 4) + +def test_input_rotary_encoder_settable(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21, max_steps=4) as encoder: + assert encoder.max_steps == 4 + assert encoder.steps == 0 + assert encoder.value == 0 + rotate_cw(a_pin, b_pin) + assert encoder.steps == 1 + assert encoder.value == 0.25 + encoder.steps = 0 + assert encoder.steps == 0 + assert encoder.value == 0 + rotate_ccw(a_pin, b_pin) + assert encoder.steps == -1 + assert encoder.value == -0.25 + encoder.value = 0 + assert encoder.steps == 0 + assert encoder.value == 0 + with RotaryEncoder(20, 21, max_steps=0) as encoder: + assert encoder.max_steps == 0 + assert encoder.steps == 0 + assert encoder.value == 0 + rotate_cw(a_pin, b_pin) + assert encoder.steps == 1 + # value is perpetually 0 when max_steps is 0 + assert encoder.value == 0 + encoder.steps = 0 + assert encoder.steps == 0 + assert encoder.value == 0 + +def test_input_rotary_encoder_wrap(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21, max_steps=4, wrap=True) as encoder: + assert encoder.wrap + for expected in [1, 2, 3, 4, -4, -3, -2, -1, 0]: + rotate_cw(a_pin, b_pin) + assert encoder.steps == expected + +def test_input_rotary_encoder_when(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21) as encoder: + rotated = Event() + rotated_cw = Event() + rotated_ccw = Event() + assert encoder.when_rotated is None + assert encoder.when_rotated_clockwise is None + assert encoder.when_rotated_counter_clockwise is None + encoder.when_rotated = rotated.set + encoder.when_rotated_clockwise = rotated_cw.set + encoder.when_rotated_counter_clockwise = rotated_ccw.set + assert encoder.when_rotated is not None + assert encoder.when_rotated_clockwise is not None + assert encoder.when_rotated_counter_clockwise is not None + assert callable(encoder.when_rotated) + assert callable(encoder.when_rotated_clockwise) + assert callable(encoder.when_rotated_counter_clockwise) + assert not rotated.wait(0) + assert not rotated_cw.wait(0) + assert not rotated_ccw.wait(0) + rotate_cw(a_pin, b_pin) + assert rotated.wait(0) + assert rotated_cw.wait(0) + assert not rotated_ccw.wait(0) + rotated.clear() + rotated_cw.clear() + rotate_ccw(a_pin, b_pin) + assert rotated.wait(0) + assert not rotated_cw.wait(0) + assert rotated_ccw.wait(0) + +def test_input_rotary_encoder_wait(mock_factory): + a_pin = mock_factory.pin(20) + b_pin = mock_factory.pin(21) + with RotaryEncoder(20, 21) as encoder: + # The rotary encoder waits are "pulsed", i.e. they act like edge waits + # rather than level waits hence the need for a background thread here + # that actively attempts to wait on rotation while it happens. It's + # not enough to rotate and *then* attempt to wait + test_rotate = ThreadedTest(lambda: encoder.wait_for_rotate(0)) + test_rotate_cw = ThreadedTest(lambda: encoder.wait_for_rotate_clockwise(0)) + test_rotate_ccw = ThreadedTest(lambda: encoder.wait_for_rotate_counter_clockwise(0)) + assert not test_rotate.result + assert not test_rotate_cw.result + assert not test_rotate_ccw.result + test_thread = ThreadedTest(lambda: encoder.wait_for_rotate(1)) + test_thread_cw = ThreadedTest(lambda: encoder.wait_for_rotate_clockwise(1)) + test_thread_ccw = ThreadedTest(lambda: encoder.wait_for_rotate_counter_clockwise(1)) + rotate_cw(a_pin, b_pin) + assert test_thread.result + assert test_thread_cw.result + assert not test_thread_ccw.result + test_rotate = ThreadedTest(lambda: encoder.wait_for_rotate(0)) + test_rotate_cw = ThreadedTest(lambda: encoder.wait_for_rotate_clockwise(0)) + test_rotate_ccw = ThreadedTest(lambda: encoder.wait_for_rotate_counter_clockwise(0)) + assert not test_rotate.result + assert not test_rotate_cw.result + assert not test_rotate_ccw.result + test_thread = ThreadedTest(lambda: encoder.wait_for_rotate(1)) + test_thread_cw = ThreadedTest(lambda: encoder.wait_for_rotate_clockwise(1)) + test_thread_ccw = ThreadedTest(lambda: encoder.wait_for_rotate_counter_clockwise(1)) + rotate_ccw(a_pin, b_pin) + assert test_thread.result + assert not test_thread_cw.result + assert test_thread_ccw.result diff --git a/tests/test_internal_devices.py b/tests/test_internal_devices.py new file mode 100644 index 000000000..0b66f1c47 --- /dev/null +++ b/tests/test_internal_devices.py @@ -0,0 +1,298 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2019-2021 Dave Jones +# Copyright (c) 2019 Jeevan M R <14.jeevan@gmail.com> +# Copyright (c) 2019 Ben Nuttall +# Copyright (c) 2018 SteveAmor +# +# SPDX-License-Identifier: BSD-3-Clause + +import errno +import warnings +from posix import statvfs_result +from subprocess import CalledProcessError +from threading import Event +from unittest import mock + +import pytest + +from gpiozero import * +from datetime import datetime, time + +file_not_found = IOError(errno.ENOENT, 'File not found') +bad_ping = CalledProcessError(1, 'returned non-zero exit status 1') + + +def test_polled_event_delay(mock_factory): + with TimeOfDay(time(7), time(8)) as tod: + tod.event_delay = 1.0 + assert tod.event_delay == 1.0 + +def test_timeofday_bad_init(mock_factory): + with pytest.raises(TypeError): + TimeOfDay() + with pytest.raises(ValueError): + TimeOfDay(7, 12) + with pytest.raises(TypeError): + TimeOfDay(time(7)) + with pytest.raises(ValueError): + TimeOfDay(time(7), time(7)) + with pytest.raises(ValueError): + TimeOfDay(time(7), time(7)) + with pytest.raises(ValueError): + TimeOfDay('7:00', '8:00') + with pytest.raises(ValueError): + TimeOfDay(7.00, 8.00) + with pytest.raises(ValueError): + TimeOfDay(datetime(2019, 1, 24, 19), time(19)) # lurch edge case + +def test_timeofday_init(mock_factory): + TimeOfDay(time(7), time(8), utc=False) + TimeOfDay(time(7), time(8), utc=True) + TimeOfDay(time(0), time(23, 59)) + TimeOfDay(time(0), time(23, 59)) + TimeOfDay(time(12, 30), time(13, 30)) + TimeOfDay(time(23), time(1)) + TimeOfDay(time(6), time(18)) + TimeOfDay(time(18), time(6)) + TimeOfDay(datetime(2019, 1, 24, 19), time(19, 1)) # lurch edge case + +def test_timeofday_value(mock_factory): + with TimeOfDay(time(7), time(8), utc=False) as tod: + assert repr(tod).startswith('' + + with TimeOfDay(time(1, 30), time(23, 30)) as tod: + assert tod.start_time == time(1, 30) + assert tod.end_time == time(23, 30) + assert tod.utc + with mock.patch('gpiozero.internal_devices.datetime') as dt: + dt.utcnow.return_value = datetime(2018, 1, 1, 1, 29, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 1, 30, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 12, 30, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 23, 30, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 23, 31, 0) + assert not tod.is_active + + with TimeOfDay(time(23), time(1)) as tod: + with mock.patch('gpiozero.internal_devices.datetime') as dt: + dt.utcnow.return_value = datetime(2018, 1, 1, 22, 59, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 23, 0, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 2, 1, 0, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 2, 1, 1, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 3, 12, 0, 0) + assert not tod.is_active + + with TimeOfDay(time(6), time(5)) as tod: + with mock.patch('gpiozero.internal_devices.datetime') as dt: + dt.utcnow.return_value = datetime(2018, 1, 1, 5, 30, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 5, 59, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 6, 0, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 18, 0, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 1, 5, 0, 0) + assert tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 2, 5, 1, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 2, 5, 30, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 2, 5, 59, 0) + assert not tod.is_active + dt.utcnow.return_value = datetime(2018, 1, 2, 6, 0, 0) + assert tod.is_active + +def test_polled_events(mock_factory): + with TimeOfDay(time(7), time(8)) as tod: + tod.event_delay = 0.1 + activated = Event() + deactivated = Event() + with mock.patch('gpiozero.internal_devices.datetime') as dt: + dt.utcnow.return_value = datetime(2018, 1, 1, 0, 0, 0) + tod._fire_events(tod.pin_factory.ticks(), tod.is_active) + tod.when_activated = activated.set + tod.when_deactivated = deactivated.set + assert not activated.wait(0) + assert not deactivated.wait(0) + dt.utcnow.return_value = datetime(2018, 1, 1, 7, 1, 0) + assert activated.wait(1) + activated.clear() + assert not deactivated.wait(0) + dt.utcnow.return_value = datetime(2018, 1, 1, 8, 1, 0) + assert deactivated.wait(1) + assert not activated.wait(0) + tod.when_activated = None + tod.when_deactivated = None + +def test_polled_event_start_stop(mock_factory): + with TimeOfDay(time(7), time(8)) as tod: + assert not tod._event_thread + tod.when_activated = lambda: True + assert tod._event_thread + tod.when_deactivated = lambda: True + assert tod._event_thread + tod.when_activated = None + assert tod._event_thread + tod.when_deactivated = None + assert not tod._event_thread + +def test_pingserver_bad_init(mock_factory): + with pytest.raises(TypeError): + PingServer() + +def test_pingserver_init(mock_factory): + with mock.patch('gpiozero.internal_devices.subprocess') as sp: + sp.check_call.return_value = True + with PingServer('example.com') as server: + assert repr(server).startswith('' + with PingServer('192.168.1.10') as server: + assert server.host == '192.168.1.10' + with PingServer('8.8.8.8') as server: + assert server.host == '8.8.8.8' + with PingServer('2001:4860:4860::8888') as server: + assert server.host == '2001:4860:4860::8888' + +def test_pingserver_value(mock_factory): + with mock.patch('gpiozero.internal_devices.subprocess.check_call') as check_call: + with PingServer('example.com') as server: + assert server.is_active + check_call.side_effect = bad_ping + assert not server.is_active + check_call.side_effect = None + assert server.is_active + +def test_cputemperature_bad_init(mock_factory): + with mock.patch('io.open', mock.mock_open()) as m: + m.side_effect = file_not_found + with pytest.raises(IOError): + with CPUTemperature('') as temp: + temp.value + with pytest.raises(IOError): + with CPUTemperature('badfile') as temp: + temp.value + m.return_value.__enter__.return_value.readline.return_value = '37000' + with pytest.raises(ValueError): + CPUTemperature(min_temp=100) + with pytest.raises(ValueError): + CPUTemperature(min_temp=10, max_temp=10) + with pytest.raises(ValueError): + CPUTemperature(min_temp=20, max_temp=10) + +def test_cputemperature(mock_factory): + with mock.patch('io.open', mock.mock_open(read_data='37000')) as m: + with CPUTemperature() as cpu: + assert repr(cpu).startswith('' + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + with CPUTemperature(min_temp=30, max_temp=40) as cpu: + assert cpu.value == 0.7 + assert not cpu.is_active + assert len(w) == 1 + assert w[0].category == ThresholdOutOfRange + assert cpu.temperature == 37.0 + with CPUTemperature(min_temp=30, max_temp=40, threshold=35) as cpu: + assert cpu.is_active + +def test_loadaverage_bad_init(mock_factory): + with mock.patch('io.open', mock.mock_open()) as m: + m.side_effect = file_not_found + with pytest.raises(IOError): + with LoadAverage('') as load: + load.value + with pytest.raises(IOError): + with LoadAverage('badfile') as load: + load.value + with mock.patch('io.open', mock.mock_open(read_data='0.09 0.10 0.09 1/292 20758')): + with pytest.raises(ValueError): + LoadAverage(min_load_average=1) + with pytest.raises(ValueError): + LoadAverage(min_load_average=0.5, max_load_average=0.5) + with pytest.raises(ValueError): + LoadAverage(min_load_average=1, max_load_average=0.5) + with pytest.raises(ValueError): + LoadAverage(minutes=0) + with pytest.raises(ValueError): + LoadAverage(minutes=10) + +def test_loadaverage(mock_factory): + with mock.patch('io.open', mock.mock_open(read_data='0.09 0.10 0.09 1/292 20758')): + with LoadAverage() as la: + assert repr(la).startswith('' + with mock.patch('io.open', mock.mock_open(read_data='1.72 1.40 1.31 3/457 23102')): + with LoadAverage(min_load_average=0.5, max_load_average=2, + threshold=1, minutes=5) as la: + assert la.min_load_average == 0.5 + assert la.max_load_average == 2 + assert la.threshold == 1 + assert la.load_average == 1.4 + assert la.value == 0.6 + assert la.is_active + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + with LoadAverage(min_load_average=1, max_load_average=2, + threshold=0.8, minutes=5) as la: + assert len(w) == 1 + assert w[0].category == ThresholdOutOfRange + assert la.load_average == 1.4 + +def test_diskusage_bad_init(mock_factory): + with pytest.raises(OSError): + DiskUsage(filesystem='badfilesystem') + +def test_diskusage(mock_factory): + with mock.patch('os.statvfs') as statvfs: + statvfs.return_value = statvfs_result(( + 4096, 4096, 100000, 48000, 48000, 0, 0, 0, 0, 255)) + with DiskUsage() as disk: + assert repr(disk).startswith('' + with DiskUsage(threshold=50.0) as disk: + assert disk.is_active == True + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + with DiskUsage(threshold=125) as disk: + assert disk.threshold == 125 + assert not disk.is_active + assert len(w) == 1 + assert w[0].category == ThresholdOutOfRange + assert disk.usage == 52.0 diff --git a/tests/test_mixins.py b/tests/test_mixins.py new file mode 100644 index 000000000..736e29e4b --- /dev/null +++ b/tests/test_mixins.py @@ -0,0 +1,124 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2019-2021 Dave Jones +# +# SPDX-License-Identifier: BSD-3-Clause + +import gc +import sys +from time import sleep +from threading import Event + +import pytest + +from gpiozero import * + + +def test_source_delay(mock_factory): + with OutputDevice(2) as device: + device.source_delay = 1 + assert device.source_delay == 1 + device.source_delay = 0.1 + assert device.source_delay == 0.1 + with pytest.raises(ValueError): + device.source_delay = -1 + + +def test_source(mock_factory): + pin = mock_factory.pin(4) + with InputDevice(4) as in_dev, OutputDevice(3) as out_dev: + assert out_dev.source is None + out_dev.source = in_dev.values + assert out_dev.source is not None + assert out_dev.value == 0 + pin.drive_high() + # Give the output device some time to read the input device state + sleep(0.1) + assert out_dev.value == 1 + + +def test_active_time(mock_factory): + pin = mock_factory.pin(4) + with DigitalInputDevice(4) as dev: + assert dev.active_time is None + assert dev.inactive_time >= 0.0 + pin.drive_high() + sleep(0.1) + assert dev.active_time >= 0.1 + assert dev.inactive_time is None + pin.drive_low() + sleep(0.1) + assert dev.active_time is None + assert dev.inactive_time >= 0.1 + + +def test_basic_callbacks(mock_factory): + pin = mock_factory.pin(4) + evt = Event() + with DigitalInputDevice(4) as dev: + dev.when_activated = evt.set + assert dev.when_activated is not None + pin.drive_high() + assert evt.wait(0.1) + pin.drive_low() + dev.when_activated = None + assert dev.when_activated is None + evt.clear() + pin.drive_high() + assert not evt.wait(0.1) + + +def test_builtin_callbacks(mock_factory): + pin = mock_factory.pin(4) + with DigitalInputDevice(4) as dev: + assert gc.isenabled() + dev.when_activated = gc.disable + assert dev.when_activated is gc.disable + pin.drive_high() + assert not gc.isenabled() + gc.enable() + + +def test_callback_with_param(mock_factory): + pin = mock_factory.pin(4) + with DigitalInputDevice(4) as dev: + devices = [] + evt = Event() + def cb(d): + devices.append(d) + evt.set() + dev.when_activated = cb + assert dev.when_activated is not None + pin.drive_high() + assert evt.wait(1) + assert devices == [dev] + + +def test_bad_callback(mock_factory): + pin = mock_factory.pin(4) + with DigitalInputDevice(4) as dev: + with pytest.raises(BadEventHandler): + dev.when_activated = 100 + with pytest.raises(BadEventHandler): + dev.when_activated = lambda x, y: x + y + + +def test_shared_key(mock_factory): + class SharedDevice(SharedMixin, GPIODevice): + def __init__(self, pin, pin_factory=None): + super().__init__(pin, pin_factory=pin_factory) + + @classmethod + def _shared_key(cls, pin, pin_factory=None): + return pin + + def _conflicts_with(self, other): + return not isinstance(other, SharedDevice) + + with SharedDevice(4) as dev: + with SharedDevice(4) as another_dev: + pass + with pytest.raises(GPIOPinInUse): + GPIODevice(4) diff --git a/tests/test_mock_pin.py b/tests/test_mock_pin.py new file mode 100644 index 000000000..5ab2b0298 --- /dev/null +++ b/tests/test_mock_pin.py @@ -0,0 +1,238 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2016-2019 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +from threading import Event +from time import sleep + +import pytest + +from gpiozero import * +from gpiozero.pins.mock import * + + +def test_mock_pin_init(mock_factory): + with pytest.raises(ValueError): + Device.pin_factory.pin(60) + assert Device.pin_factory.pin(2).info.name == 'GPIO2' + + +def test_mock_pin_defaults(mock_factory): + pin = Device.pin_factory.pin(4) + assert pin.bounce == None + assert pin.edges == 'both' + assert pin.frequency == None + assert pin.function == 'input' + assert pin.pull == 'floating' + assert pin.state == 0 + assert pin.when_changed == None + pin.close() + pin = Device.pin_factory.pin(2) + assert pin.pull == 'up' + + +def test_mock_pin_open_close(mock_factory): + pin = Device.pin_factory.pin(2) + pin.close() + + +def test_mock_pin_init_twice_same_pin(mock_factory): + pin1 = Device.pin_factory.pin(2) + pin2 = Device.pin_factory.pin(pin1.info.name) + assert pin1 is pin2 + + +def test_mock_pin_init_twice_different_pin(mock_factory): + pin1 = Device.pin_factory.pin(2) + pin2 = Device.pin_factory.pin(3) + assert pin1 != pin2 + assert pin1.info.name == 'GPIO2' + assert pin2.info.name == 'GPIO3' + + +def test_mock_pwm_pin_defaults(mock_factory): + pin = Device.pin_factory.pin(4, pin_class=MockPWMPin) + assert pin.bounce == None + assert pin.edges == 'both' + assert pin.frequency == None + assert pin.function == 'input' + assert pin.pull == 'floating' + assert pin.state == 0 + assert pin.when_changed == None + pin.close() + pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) + assert pin.pull == 'up' + + +def test_mock_pwm_pin_open_close(mock_factory): + pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) + pin.close() + + +def test_mock_pwm_pin_init_twice_same_pin(mock_factory): + pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin) + pin2 = Device.pin_factory.pin(pin1.info.name, pin_class=MockPWMPin) + assert pin1 is pin2 + + +def test_mock_pwm_pin_init_twice_different_pin(mock_factory): + pin1 = Device.pin_factory.pin(2, pin_class=MockPWMPin) + pin2 = Device.pin_factory.pin(3, pin_class=MockPWMPin) + assert pin1 != pin2 + assert pin1.info.name == 'GPIO2' + assert pin2.info.name == 'GPIO3' + + +def test_mock_pin_init_twice_different_modes(mock_factory): + pin1 = Device.pin_factory.pin(2, pin_class=MockPin) + pin2 = Device.pin_factory.pin(3, pin_class=MockPWMPin) + assert pin1 != pin2 + with pytest.raises(ValueError): + Device.pin_factory.pin(pin1.info.name, pin_class=MockPWMPin) + with pytest.raises(ValueError): + Device.pin_factory.pin(pin2.info.name, pin_class=MockPin) + + +def test_mock_pin_frequency_unsupported(mock_factory): + pin = Device.pin_factory.pin(2) + pin.frequency = None + with pytest.raises(PinPWMUnsupported): + pin.frequency = 100 + + +def test_mock_pin_frequency_supported(mock_factory): + pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) + pin.function = 'output' + assert pin.frequency is None + pin.frequency = 100 + pin.state = 0.5 + pin.frequency = None + assert not pin.state + + +def test_mock_pin_pull(mock_factory): + pin = Device.pin_factory.pin(4) + pin.function = 'input' + assert pin.pull == 'floating' + pin.pull = 'up' + assert pin.state + pin.pull = 'down' + assert not pin.state + with pytest.raises(PinInvalidPull): + pin.pull = 'foo' + pin.function = 'output' + with pytest.raises(PinFixedPull): + pin.pull = 'floating' + pin.close() + pin = Device.pin_factory.pin(2) + pin.function = 'input' + assert pin.pull == 'up' + with pytest.raises(PinFixedPull): + pin.pull = 'floating' + + +def test_mock_pin_state(mock_factory): + pin = Device.pin_factory.pin(2) + with pytest.raises(PinSetInput): + pin.state = 1 + pin.function = 'output' + pin.state = 1 + assert pin.state == 1 + pin.state = 0 + assert pin.state == 0 + pin.state = 0.5 + assert pin.state == 1 + + +def test_mock_pin_connected_state(mock_factory): + input_pin = Device.pin_factory.pin(4) + pin2 = Device.pin_factory.pin(5, pin_class=MockConnectedPin, input_pin=input_pin) + pin3 = Device.pin_factory.pin(6, pin_class=MockConnectedPin) + input_pin.function = 'input' + pin2.function = 'output' + pin3.function = 'output' + pin2.state = 0 + assert input_pin.state == 0 + pin2.state = 1 + assert input_pin.state == 1 + pin3.state = 0 + assert input_pin.state == 1 + pin3.state = 1 + assert input_pin.state == 1 + + +def test_mock_pin_functions(mock_factory): + pin = Device.pin_factory.pin(2) + assert pin.function == 'input' + pin.function = 'output' + assert pin.function == 'output' + pin.function = 'input' + assert pin.function == 'input' + with pytest.raises(ValueError): + pin.function = 'foo' + + +def test_mock_pwm_pin_state(mock_factory): + pin = Device.pin_factory.pin(2, pin_class=MockPWMPin) + with pytest.raises(PinSetInput): + pin.state = 1 + pin.function = 'output' + pin.state = 1 + assert pin.state == 1 + pin.state = 0 + assert pin.state == 0 + pin.state = 0.5 + assert pin.state == 0.5 + + +def test_mock_pin_edges(mock_factory): + pin = Device.pin_factory.pin(2) + assert pin.when_changed is None + fired = Event() + pin.function = 'input' + pin.edges = 'both' + assert pin.edges == 'both' + pin.drive_low() + assert not pin.state + def changed(ticks, state): + fired.set() + pin.when_changed = changed + pin.drive_high() + assert pin.state + assert fired.is_set() + fired.clear() + pin.edges = 'falling' + pin.drive_low() + assert not pin.state + assert fired.is_set() + fired.clear() + pin.drive_high() + assert pin.state + assert not fired.is_set() + assert pin.edges == 'falling' + + +def test_mock_charging_pin(mock_factory): + pin = Device.pin_factory.pin(4, pin_class=MockChargingPin, charge_time=1) + pin.function = 'input' + assert pin.state == 0 + pin.function = 'output' + assert pin.state == 0 + pin.close() + mock_factory.reset() + pin = Device.pin_factory.pin(4, pin_class=MockChargingPin, charge_time=0.01) + pin.function = 'input' + sleep(0.1) + assert pin.state == 1 + pin.function = 'output' + pin.state = 0 + pin.function = 'output' + assert pin.state == 0 + pin.function = 'input' + sleep(0.1) + assert pin.state == 1 diff --git a/tests/test_outputs.py b/tests/test_outputs.py new file mode 100644 index 000000000..22d23979a --- /dev/null +++ b/tests/test_outputs.py @@ -0,0 +1,1563 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2018-2019 Ben Nuttall +# Copyright (c) 2016 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import sys +from time import sleep, time +from math import isclose + +import pytest +from colorzero import Color, Red, Green, Blue + +from gpiozero import * +from gpiozero.tones import Tone + + +def test_output_initial_values(mock_factory, pwm): + pin = mock_factory.pin(2) + with OutputDevice(2, initial_value=False) as device: + assert repr(device).startswith('' + with OutputDevice(2, initial_value=True) as device: + assert pin.state + state = pin.state + with OutputDevice(2, initial_value=None) as device: + assert state == pin.state + +def test_output_write_active_high(mock_factory): + pin = mock_factory.pin(2) + with OutputDevice(2) as device: + device.on() + assert pin.state + device.off() + assert not pin.state + +def test_output_write_active_low(mock_factory): + pin = mock_factory.pin(2) + with OutputDevice(2, active_high=False) as device: + device.on() + assert not pin.state + device.off() + assert pin.state + +def test_output_write_closed(mock_factory): + with OutputDevice(2) as device: + device.close() + assert device.closed + device.close() + assert device.closed + with pytest.raises(GPIODeviceClosed): + device.on() + +def test_output_write_silly(mock_factory): + pin = mock_factory.pin(2) + with OutputDevice(2) as device: + pin.function = 'input' + with pytest.raises(AttributeError): + device.on() + +def test_output_value(mock_factory): + pin = mock_factory.pin(2) + with OutputDevice(2) as device: + assert not device.value + assert not pin.state + device.on() + assert device.value + assert pin.state + device.value = False + assert not device.value + assert not pin.state + +def test_output_digital_toggle(mock_factory): + pin = mock_factory.pin(2) + with DigitalOutputDevice(2) as device: + assert repr(device).startswith('' + with TonalBuzzer(2, mid_tone='C4') as tb: + assert tb.pwm_device.frequency is None + with TonalBuzzer(2, mid_tone='C4', initial_value=0) as tb: + assert isclose(tb.pwm_device.frequency, 261.626, abs_tol=1/100) + with TonalBuzzer(2, initial_value=-1) as tb: + assert isclose(tb.pwm_device.frequency, 220) + with TonalBuzzer(2, initial_value=0) as tb: + assert isclose(tb.pwm_device.frequency, 440) + with TonalBuzzer(2, initial_value=1) as tb: + assert isclose(tb.pwm_device.frequency, 880) + with TonalBuzzer(2, octaves=2, initial_value=-1) as tb: + assert isclose(tb.pwm_device.frequency, 110) + with TonalBuzzer(2, octaves=2, initial_value=0) as tb: + assert isclose(tb.pwm_device.frequency, 440) + with TonalBuzzer(2, octaves=2, initial_value=1) as tb: + assert isclose(tb.pwm_device.frequency, 1760) + +@pytest.mark.filterwarnings('ignore::gpiozero.exc.AmbiguousTone') +def test_tonalbuzzer_play(mock_factory, pwm): + with TonalBuzzer(2) as tb: + tb.play(60) + assert isclose(tb.pwm_device.frequency, 261.626, abs_tol=1/100) + tb.play(None) + assert tb.value is None + assert tb.pwm_device.frequency is None + tb.play('C5') + assert isclose(tb.pwm_device.frequency, 523.25, abs_tol=1/100) + tb.play(Tone('A#4')) + assert isclose(tb.pwm_device.frequency, 466.16, abs_tol=1/100) + tb.stop() + assert tb.value is None + assert tb.pwm_device.frequency is None + with pytest.raises(ValueError): + tb.play('G#3') + with pytest.raises(ValueError): + tb.play('A#5') + +def test_tonalbuzzer_set_value(mock_factory, pwm): + with TonalBuzzer(2) as tb: + assert tb.pwm_device.frequency is None + tb.value = -1 + assert isclose(tb.pwm_device.frequency, 220) + tb.value = 1 + assert isclose(tb.pwm_device.frequency, 880) + with TonalBuzzer(2, octaves=2) as tb: + assert tb.pwm_device.frequency is None + tb.value = -1 + assert isclose(tb.pwm_device.frequency, 110) + tb.value = 1 + assert isclose(tb.pwm_device.frequency, 1760) + +def test_tonalbuzzer_read_value(mock_factory, pwm): + with TonalBuzzer(2) as tb: + assert tb.value is None + tb.play('A3') + assert isclose(tb.value, -1) + tb.play('A4') + assert isclose(tb.value, 0) + tb.play('A5') + assert isclose(tb.value, 1) + with TonalBuzzer(2, octaves=2) as tb: + assert tb.value is None + tb.play('A2') + assert isclose(tb.value, -1) + tb.play('A3') + assert isclose(tb.value, -0.5) + tb.play('A4') + assert isclose(tb.value, 0) + tb.play('A5') + assert isclose(tb.value, 0.5) + tb.play('A6') + assert isclose(tb.value, 1) + +def test_tonalbuzzer_tone(mock_factory, pwm): + with TonalBuzzer(2) as tb: + assert tb.tone is None + tb.play('A3') + assert tb.tone == Tone('A3') + tb.play('A4') + assert tb.tone == Tone('A4') + tb.play('A5') + assert tb.tone == Tone('A5') + tb.tone = None + assert tb.tone is None diff --git a/tests/test_pins_data.py b/tests/test_pins_data.py new file mode 100644 index 000000000..adffe0d12 --- /dev/null +++ b/tests/test_pins_data.py @@ -0,0 +1,297 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2019 Ben Nuttall +# Copyright (c) 2018 Martchus +# +# SPDX-License-Identifier: BSD-3-Clause + +import io +import re +import errno +import pytest +from unittest import mock + +import gpiozero.pins.data +import gpiozero.pins.local +from gpiozero.pins.local import LocalPiFactory +from gpiozero.pins.pi import PiBoardInfo +from gpiozero.pins.style import Style +from gpiozero.compat import frozendict +from gpiozero import * + + +def test_pi_revision(): + with mock.patch('gpiozero.devices.Device.pin_factory', LocalPiFactory()): + # Can't use MockPin for this as we want something that'll actually try + # and read /proc/device-tree/system/linux,revision and /proc/cpuinfo + # (MockPin simply parrots the 3B's data); LocalPiFactory is used as we + # can definitely instantiate it (strictly speaking it's abstract but + # we're only interested in the BoardInfo stuff) + with mock.patch('io.open') as m: + m.return_value.__enter__.side_effect = [ + # Pretend /proc/device-tree/system/linux,revision doesn't + # exist, and that /proc/cpuinfo contains the Revision: 0002 + # after some filler + IOError(errno.ENOENT, 'File not found'), + ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx'] + ] + assert Device.pin_factory.board_info.revision == '0002' + # LocalPiFactory caches the revision (because realistically it + # isn't going to change at runtime); we need to wipe it here though + Device.pin_factory._info = None + m.return_value.__enter__.side_effect = [ + IOError(errno.ENOENT, 'File not found'), + ['Revision: a21042'] + ] + assert Device.pin_factory.board_info.revision == 'a21042' + # Check over-volting result (some argument over whether this is 7 + # or 8 character result; make sure both work) + Device.pin_factory._info = None + m.return_value.__enter__.side_effect = [ + IOError(errno.ENOENT, 'File not found'), + ['Revision: 1000003'] + ] + assert Device.pin_factory.board_info.revision == '0003' + Device.pin_factory._info = None + m.return_value.__enter__.side_effect = [ + IOError(errno.ENOENT, 'File not found'), + ['Revision: 100003'] + ] + assert Device.pin_factory.board_info.revision == '0003' + # Check we complain loudly if we can't access linux,revision + Device.pin_factory._info = None + m.return_value.__enter__.side_effect = [ + # Pretend /proc/device-tree/system/linux,revision doesn't + # exist, and that /proc/cpuinfo contains the Revision: 0002 + # after some filler + IOError(errno.EACCES, 'Permission denied'), + ['Revision: 100003'] + ] + with pytest.raises(IOError): + Device.pin_factory.board_info + # Check that parsing /proc/device-tree/system/linux,revision also + # works properly + Device.pin_factory._info = None + m.return_value.__enter__.side_effect = None + m.return_value.__enter__.return_value = io.BytesIO(b'\x00\xa2\x20\xd3') + assert Device.pin_factory.board_info.revision == 'a220d3' + # Check that if everything's a bust we raise PinUnknownPi + with pytest.raises(PinUnknownPi): + Device.pin_factory._info = None + m.return_value.__enter__.return_value = None + m.return_value.__enter__.side_effect = [ + IOError(errno.ENOENT, 'File not found'), + ['nothing', 'relevant'] + ] + Device.pin_factory.board_info + with pytest.raises(PinUnknownPi): + PiBoardInfo.from_revision(0xfff) + +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +def test_pi_info(): + r = pi_info('900011') + assert r.model == 'B' + assert r.pcb_revision == '1.0' + assert r.memory == 512 + assert r.manufacturer == 'Sony' + assert r.storage == 'SD' + assert r.usb == 2 + assert r.ethernet == 1 + assert not r.wifi + assert not r.bluetooth + assert r.csi == 1 + assert r.dsi == 1 + r = pi_info('9000f1') + assert r.model == '???' + assert r.pcb_revision == '1.1' + assert r.memory == 512 + assert r.manufacturer == 'Sony' + assert r.storage == 'MicroSD' + assert r.usb == 4 + assert r.ethernet == 1 + assert not r.wifi + assert not r.bluetooth + assert r.csi == 1 + assert r.dsi == 1 + assert repr(r).startswith('PiBoardInfo(revision=') + assert 'headers=...' in repr(r) + +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +def test_pi_info_other_types(): + assert pi_info(b'9000f1') == pi_info(0x9000f1) + +def test_find_pin(): + board_info = PiBoardInfo.from_revision(0xa21041) + assert {('J8', 1), ('J8', 17)} == { + (head.name, pin.number) + for (head, pin) in board_info.find_pin('3V3') + } + assert {('J8', 3)} == { + (head.name, pin.number) + for (head, pin) in board_info.find_pin('GPIO2') + } + assert set() == { + (head.name, pin.number) + for (head, pin) in board_info.find_pin('GPIO47') + } + +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +def test_physical_pins(): + # Assert physical pins for some well-known Pi's; a21041 is a Pi2B + board_info = PiBoardInfo.from_revision(0xa21041) + assert board_info.physical_pins('3V3') == {('J8', 1), ('J8', 17)} + assert board_info.physical_pins('GPIO2') == {('J8', 3)} + assert board_info.physical_pins('GPIO47') == set() + +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +def test_physical_pin(): + board_info = PiBoardInfo.from_revision(0xa21041) + with pytest.raises(PinMultiplePins): + assert board_info.physical_pin('GND') + assert board_info.physical_pin('GPIO3') == ('J8', 5) + with pytest.raises(PinNoPins): + assert board_info.physical_pin('GPIO47') + +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +def test_pulled_up(): + board_info = PiBoardInfo.from_revision(0xa21041) + assert board_info.pulled_up('GPIO2') + assert not board_info.pulled_up('GPIO4') + assert not board_info.pulled_up('GPIO47') + +def test_pull(): + board_info = PiBoardInfo.from_revision(0xa21041) + for header, pin in board_info.find_pin('GPIO2'): + assert pin.pull == 'up' + for header, pin in board_info.find_pin('GPIO4'): + assert pin.pull == '' + for header, pin in board_info.find_pin('GPIO47'): + assert pin.pull == '' + +def test_pprint_content(capsys): + PiBoardInfo.from_revision(0x900092).pprint(color=False) + cap = capsys.readouterr() + assert ('-' + 'o' * 20) in cap.out # first header row + assert (' ' + '1' + 'o' * 19) in cap.out # second header row + assert 'PiZero' in cap.out + assert 'V1.2' in cap.out # PCB revision + assert '900092' in cap.out # Pi revision + assert 'BCM2835' in cap.out # SoC name + PiBoardInfo.from_revision(0x0002).pprint(color=False) + cap = capsys.readouterr() + assert ('o' * 13 + ' ') in cap.out # first header row + assert ('1' + 'o' * 12 + ' ') in cap.out # second header row + assert 'Pi Model' in cap.out + assert 'B V1.0' in cap.out # PCB revision + assert '0002' in cap.out # Pi revision + assert 'BCM2835' in cap.out # SOC name + PiBoardInfo.from_revision(0x0014).headers['SODIMM'].pprint(color=False) + cap = capsys.readouterr() + assert len(cap.out.splitlines()) == 100 + +def test_format_content(capsys): + board_info = PiBoardInfo.from_revision(0x900092) + board_info.pprint(color=False) + cap = capsys.readouterr() + assert f'{board_info:mono}\n' == cap.out + board_info.pprint(color=True) + cap = capsys.readouterr() + assert f'{board_info:color full}\n' == cap.out + with pytest.raises(ValueError): + f'{board_info:color foo}' + +def test_pprint_headers(capsys): + assert len(PiBoardInfo.from_revision(0x0002).headers) == 3 + assert len(PiBoardInfo.from_revision(0x000e).headers) == 5 + assert len(PiBoardInfo.from_revision(0x900092).headers) == 3 + PiBoardInfo.from_revision(0x0002).pprint() + cap = capsys.readouterr() + assert 'P1:\n' in cap.out + assert 'P5:\n' not in cap.out + PiBoardInfo.from_revision(0x000e).pprint() + cap = capsys.readouterr() + assert 'P1:\n' in cap.out + assert 'P5:\n' in cap.out + PiBoardInfo.from_revision(0x900092).pprint() + cap = capsys.readouterr() + assert 'J8:\n' in cap.out + assert 'P1:\n' not in cap.out + assert 'P5:\n' not in cap.out + +def test_format_headers(capsys): + board_info = PiBoardInfo.from_revision(0xc03131) + board_info.headers['J8'].pprint(color=False) + cap = capsys.readouterr() + assert f'{board_info.headers["J8"]:mono}\n' == cap.out + board_info.headers['J8'].pprint(color=True) + cap = capsys.readouterr() + assert f'{board_info.headers["J8"]:color}\n' == cap.out + with pytest.raises(ValueError): + f'{board_info.headers["J8"]:mono foo}' + +def test_pprint_color(capsys): + board_info = PiBoardInfo.from_revision(0x900092) + board_info.pprint(color=False) + cap = capsys.readouterr() + assert '\x1b[0m' not in cap.out + board_info.pprint(color=True) + cap = capsys.readouterr() + assert '\x1b[0m' in cap.out + +def test_pprint_style_detect(): + board_info = PiBoardInfo.from_revision(0x900092) + with mock.patch('sys.stdout') as stdout: + stdout.output = [] + stdout.write = lambda buf: stdout.output.append(buf) + stdout.fileno.side_effect = IOError('not a real file') + board_info.pprint() + s = ''.join(stdout.output) + assert '\x1b[0m' not in s # default should output mono + with mock.patch('os.isatty') as isatty: + isatty.return_value = True + stdout.fileno.side_effect = None + stdout.fileno.return_value = 1 + stdout.output = [] + board_info.pprint() + s = ''.join(stdout.output) + assert '\x1b[0m' in s # default should now output color + +def test_style_parser(): + with pytest.raises(ValueError): + Style.from_style_content('mono color full') + with pytest.raises(ValueError): + f'{Style():foo on bar}' + +def test_pprint_missing_pin(capsys): + header = HeaderInfo('FOO', 4, 2, { + 1: PinInfo(1, '5V', {'5V'}, '', 1, 1, set()), + 2: PinInfo(2, 'GND', {'GND'}, '', 1, 2, set()), + # Pin 3 is deliberately missing + 4: PinInfo(4, 'GPIO1', {'GPIO1'}, '', 2, 2, {'gpio'}), + 5: PinInfo(5, 'GPIO2', {'GPIO2'}, 'up', 3, 1, {'gpio'}), + 6: PinInfo(6, 'GPIO3', {'GPIO3'}, 'up', 3, 2, {'gpio'}), + 7: PinInfo(7, '3V3', {'3V3'}, '', 4, 1, set()), + 8: PinInfo(8, 'GND', {'GND'}, '', 4, 2, set()), + }) + header.pprint() + cap = capsys.readouterr() + for i in range(1, 9): + if i == 3: + assert '(3)' not in cap.out + else: + assert f'({i:d})' in cap.out + +def test_pprint_rows_cols(): + board_info = PiBoardInfo.from_revision(0x900092) + assert f'{board_info.headers["J8"]:row1}' == '1o' + assert f'{board_info.headers["J8"]:row2}' == 'oo' + board_info = PiBoardInfo.from_revision(0x0002) + assert f'{board_info.headers["P1"]:col1}' == '1oooooooooooo' + assert f'{board_info.headers["P1"]:col2}' == 'ooooooooooooo' + with pytest.raises(ValueError): + f'{board_info.headers["P1"]:row16}' + with pytest.raises(ValueError): + f'{board_info.headers["P1"]:col3}' diff --git a/tests/test_real_pins.py b/tests/test_real_pins.py new file mode 100644 index 000000000..7fff6d48c --- /dev/null +++ b/tests/test_real_pins.py @@ -0,0 +1,464 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2024 Dave Jones +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2020 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import os +import errno +import warnings +from time import time, sleep +from math import isclose +from unittest import mock + +# 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 + +import pytest + +from gpiozero import * +from gpiozero.pins.mock import MockConnectedPin, MockFactory, MockSPIDevice +from gpiozero.pins.native import NativeFactory +from gpiozero.pins.local import LocalPiFactory, LocalPiHardwareSPI + + +# This module assumes you've wired the following GPIO pins together. The pins +# can be re-configured via the listed environment variables (useful for when +# your testing rig requires different pins because the defaults interfere with +# attached hardware). Please note that the name specified *must* be the primary +# name of the pin, e.g. GPIO22 rather than an alias like BCM22 or 22 (several +# tests rely upon this). +TEST_PIN = os.environ.get('GPIOZERO_TEST_PIN', 'GPIO22') +INPUT_PIN = os.environ.get('GPIOZERO_TEST_INPUT_PIN', 'GPIO27') + +# The lock path is intended to prevent parallel runs of the "real pins" test +# suite. For example, if you are testing multiple Python versions under tox, +# the mocked devices will all happily test in parallel. However, the real pins +# test must not or you risk one run trying to set a pin to an output while +# another simultaneously demands it's an input. The path specified here must be +# visible and accessible to all simultaneous runs. +TEST_LOCK = os.environ.get('GPIOZERO_TEST_LOCK', '/tmp/real_pins_lock') + + +def local_only(): + if not isinstance(Device.pin_factory, LocalPiFactory): + pytest.skip("Test cannot run with non-local pin factories") + +def local_hardware_spi_only(intf): + if not isinstance(intf, LocalPiHardwareSPI): + pytest.skip("Test assumes LocalPiHardwareSPI descendant") + + +with warnings.catch_warnings(): + @pytest.fixture( + scope='module', + params=[ep.name for ep in entry_points(group='gpiozero_pin_factories')]) + def pin_factory_name(request): + return request.param + + +@pytest.fixture() +def pin_factory(request, pin_factory_name): + try: + with warnings.catch_warnings(): + eps = entry_points(group='gpiozero_pin_factories') + for ep in eps: + if ep.name == pin_factory_name: + factory = ep.load()() + break + else: + assert False, 'internal error' + except Exception as e: + pytest.skip("skipped factory {pin_factory_name}: {e!s}".format( + pin_factory_name=pin_factory_name, e=e)) + else: + yield factory + factory.close() + + +@pytest.fixture() +def default_factory(request, pin_factory): + save_pin_factory = Device.pin_factory + Device.pin_factory = pin_factory + yield pin_factory + Device.pin_factory = save_pin_factory + + +@pytest.fixture(scope='function') +def pins(request, pin_factory): + # Why return both pins in a single fixture? If we defined one fixture for + # each pin then pytest will (correctly) test RPiGPIOPin(22) against + # NativePin(27) and so on. This isn't supported, so we don't test it + assert not ( + {INPUT_PIN, TEST_PIN} & { + 'GPIO2', + 'GPIO3', + 'GPIO7', + 'GPIO8', + 'GPIO9', + 'GPIO10', + 'GPIO11', + }), 'Cannot use SPI (7-11) or I2C (2-3) pins for tests' + input_pin = pin_factory.pin(INPUT_PIN) + input_pin.function = 'input' + input_pin.pull = 'down' + if isinstance(pin_factory, MockFactory): + test_pin = pin_factory.pin(TEST_PIN, pin_class=MockConnectedPin, input_pin=input_pin) + else: + test_pin = pin_factory.pin(TEST_PIN) + yield test_pin, input_pin + test_pin.close() + input_pin.close() + + +def setup_module(module): + start = time() + while True: + if time() - start > 300: # 5 minute timeout + raise RuntimeError('timed out waiting for real pins lock') + try: + with open(TEST_LOCK, 'x') as f: + f.write('Lock file for gpiozero real-pin tests; delete ' + 'this if the test suite is not currently running\n') + except FileExistsError: + print('Waiting for lock before testing real-pins') + sleep(1) + else: + break + + +def teardown_module(module): + os.unlink(TEST_LOCK) + + +def test_pin_names(pins): + test_pin, input_pin = pins + assert test_pin.info.name == TEST_PIN + assert input_pin.info.name == INPUT_PIN + + +def test_function_bad(pins): + test_pin, input_pin = pins + with pytest.raises(PinInvalidFunction): + test_pin.function = 'foo' + + +def test_output(pins): + test_pin, input_pin = pins + test_pin.function = 'output' + test_pin.state = 0 + assert input_pin.state == 0 + test_pin.state = 1 + assert input_pin.state == 1 + + +def test_output_with_state(pins): + test_pin, input_pin = pins + test_pin.output_with_state(0) + assert input_pin.state == 0 + test_pin.output_with_state(1) + assert input_pin.state == 1 + + +def test_pull(pins): + test_pin, input_pin = pins + input_pin.pull = 'floating' + test_pin.function = 'input' + test_pin.pull = 'up' + assert test_pin.state == 1 + assert input_pin.state == 1 + test_pin.pull = 'down' + assert test_pin.state == 0 + assert input_pin.state == 0 + + +def test_pull_bad(pins): + test_pin, input_pin = pins + test_pin.function = 'input' + with pytest.raises(PinInvalidPull): + test_pin.pull = 'foo' + with pytest.raises(PinInvalidPull): + test_pin.input_with_pull('foo') + + +def test_pull_down_warning(pin_factory): + with pin_factory.pin('GPIO2') as pin: + if pin.info.pull != 'up': + pytest.skip("GPIO2 isn't pulled up on this pi") + with pytest.raises(PinFixedPull): + pin.pull = 'down' + with pytest.raises(PinFixedPull): + pin.input_with_pull('down') + + +def test_input_with_pull(pins): + test_pin, input_pin = pins + input_pin.pull = 'floating' + test_pin.input_with_pull('up') + assert test_pin.state == 1 + assert input_pin.state == 1 + test_pin.input_with_pull('down') + assert test_pin.state == 0 + assert input_pin.state == 0 + + +def test_pulls_are_weak(pins): + test_pin, input_pin = pins + test_pin.function = 'output' + for pull in ('floating', 'down', 'up'): + input_pin.pull = pull + test_pin.state = 0 + assert input_pin.state == 0 + test_pin.state = 1 + assert input_pin.state == 1 + + +def test_bad_duty_cycle(pins): + test_pin, input_pin = pins + test_pin.function = 'output' + try: + # NOTE: There's some race in RPi.GPIO that causes a segfault if we + # don't pause before starting PWM; only seems to happen when stopping + # and restarting PWM very rapidly (i.e. between test cases). + if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory': + sleep(0.1) + test_pin.frequency = 100 + except PinPWMUnsupported: + pytest.skip("{test_pin.factory!r} doesn't support PWM".format( + test_pin=test_pin)) + else: + try: + with pytest.raises(ValueError): + test_pin.state = 1.1 + finally: + test_pin.frequency = None + + +def test_duty_cycles(pins): + test_pin, input_pin = pins + test_pin.function = 'output' + try: + # NOTE: see above + if Device.pin_factory.__class__.__name__ == 'RPiGPIOFactory': + sleep(0.1) + test_pin.frequency = 100 + except PinPWMUnsupported: + pytest.skip("{test_pin.factory!r} doesn't support PWM".format( + test_pin=test_pin)) + else: + try: + for duty_cycle in (0.0, 0.1, 0.5, 1.0): + test_pin.state = duty_cycle + assert test_pin.state == duty_cycle + total = sum(input_pin.state for i in range(20000)) + assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1) + finally: + test_pin.frequency = None + + +def test_explicit_factory(no_default_factory, pin_factory): + with GPIODevice(TEST_PIN, pin_factory=pin_factory) as device: + assert Device.pin_factory is None + assert device.pin_factory is pin_factory + assert device.pin.info.name == TEST_PIN + + +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +def test_envvar_factory(no_default_factory, pin_factory_name): + os.environ['GPIOZERO_PIN_FACTORY'] = pin_factory_name + assert Device.pin_factory is None + try: + device = GPIODevice(TEST_PIN) + except Exception as e: + pytest.skip("skipped factory {pin_factory_name}: {e!s}".format( + pin_factory_name=pin_factory_name, e=e)) + else: + try: + group = entry_points(group='gpiozero_pin_factories') + for ep in group: + if ep.name == pin_factory_name: + factory_class = ep.load() + break + else: + assert False, 'internal error' + assert isinstance(Device.pin_factory, factory_class) + assert device.pin_factory is Device.pin_factory + assert device.pin.info.name == TEST_PIN + finally: + device.close() + Device.pin_factory.close() + + +def test_compatibility_names(no_default_factory): + os.environ['GPIOZERO_PIN_FACTORY'] = 'NATIVE' + try: + device = GPIODevice(TEST_PIN) + except Exception as e: + pytest.skip("skipped factory {pin_factory_name}: {e!s}".format( + pin_factory_name=pin_factory_name, e=e)) + else: + try: + assert isinstance(Device.pin_factory, NativeFactory) + assert device.pin_factory is Device.pin_factory + assert device.pin.info.name == TEST_PIN + finally: + device.close() + Device.pin_factory.close() + + +def test_bad_factory(no_default_factory): + os.environ['GPIOZERO_PIN_FACTORY'] = 'foobarbaz' + # Waits for someone to implement the foobarbaz pin factory just to + # mess with our tests ... + with pytest.raises(BadPinFactory): + GPIODevice(TEST_PIN) + + +@pytest.mark.filterwarnings('ignore::gpiozero.exc.PinFactoryFallback') +def test_default_factory(no_default_factory): + assert Device.pin_factory is None + os.environ.pop('GPIOZERO_PIN_FACTORY', None) + try: + device = GPIODevice(TEST_PIN) + except Exception as e: + pytest.skip("no default factories") + else: + try: + assert device.pin_factory is Device.pin_factory + assert device.pin.info.name == TEST_PIN + finally: + device.close() + Device.pin_factory.close() + + +def test_spi_init(pin_factory): + with pin_factory.spi() as intf: + assert isinstance(intf, SPI) + assert repr(intf) in ( + "SPI(clock_pin='GPIO11', mosi_pin='GPIO10', miso_pin='GPIO9', " + "select_pin='GPIO8')", + "SPI(port=0, device=0)" + ) + intf.close() + assert intf.closed + assert repr(intf) == 'SPI(closed)' + with pin_factory.spi(port=0, device=1) as intf: + assert repr(intf) in ( + "SPI(clock_pin='GPIO11', mosi_pin='GPIO10', miso_pin='GPIO9', " + "select_pin='GPIO7')", + "SPI(port=0, device=1)" + ) + with pin_factory.spi(clock_pin=11, mosi_pin=10, select_pin=8) as intf: + assert repr(intf) in ( + "SPI(clock_pin='GPIO11', mosi_pin='GPIO10', miso_pin='GPIO9', " + "select_pin='GPIO8')", + "SPI(port=0, device=0)" + ) + # Ensure we support "partial" SPI where we don't reserve a pin because + # the device wants it for general IO (see SPI screens which use a pin + # for data/commands) + with pin_factory.spi(clock_pin=11, mosi_pin=10, miso_pin=None, select_pin=7) as intf: + assert isinstance(intf, SPI) + with pin_factory.spi(clock_pin=11, mosi_pin=None, miso_pin=9, select_pin=7) as intf: + assert isinstance(intf, SPI) + with pin_factory.spi(shared=True) as intf: + assert isinstance(intf, SPI) + with pytest.raises(ValueError): + pin_factory.spi(port=1) + with pytest.raises(ValueError): + pin_factory.spi(device=2) + with pytest.raises(ValueError): + pin_factory.spi(port=0, clock_pin=12) + with pytest.raises(ValueError): + pin_factory.spi(foo='bar') + + +def test_spi_hardware_conflict(default_factory): + with LED(11) as led: + with pytest.raises(GPIOPinInUse): + Device.pin_factory.spi(port=0, device=0) + with Device.pin_factory.spi(port=0, device=0) as spi: + with pytest.raises(GPIOPinInUse): + LED(11) + + +def test_spi_hardware_same_port(default_factory): + with Device.pin_factory.spi(device=0) as intf: + local_hardware_spi_only(intf) + with pytest.raises(GPIOPinInUse): + Device.pin_factory.spi(device=0) + with Device.pin_factory.spi(device=1) as another_intf: + assert intf._port == another_intf._port + + +def test_spi_hardware_shared_bus(default_factory): + with Device.pin_factory.spi(device=0, shared=True) as intf: + with Device.pin_factory.spi(device=0, shared=True) as another_intf: + assert intf is another_intf + + +def test_spi_hardware_read(default_factory): + local_only() + with mock.patch('gpiozero.pins.local.SpiDev') as spidev: + spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] + with Device.pin_factory.spi() as intf: + local_hardware_spi_only(intf) + assert intf.read(3) == [0, 1, 2] + assert intf.read(6) == list(range(6)) + + +def test_spi_hardware_write(default_factory): + local_only() + with mock.patch('gpiozero.pins.local.SpiDev') as spidev: + spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)] + with Device.pin_factory.spi() as intf: + local_hardware_spi_only(intf) + assert intf.write([0, 1, 2]) == 3 + assert spidev.return_value.xfer2.called_with([0, 1, 2]) + assert intf.write(list(range(6))) == 6 + assert spidev.return_value.xfer2.called_with(list(range(6))) + + +def test_spi_hardware_modes(default_factory): + local_only() + with mock.patch('gpiozero.pins.local.SpiDev') as spidev: + spidev.return_value.mode = 0 + spidev.return_value.lsbfirst = False + spidev.return_value.cshigh = True + spidev.return_value.bits_per_word = 8 + with Device.pin_factory.spi() as intf: + local_hardware_spi_only(intf) + assert intf.clock_mode == 0 + assert not intf.clock_polarity + assert not intf.clock_phase + intf.clock_polarity = False + assert intf.clock_mode == 0 + intf.clock_polarity = True + assert intf.clock_mode == 2 + intf.clock_phase = True + assert intf.clock_mode == 3 + assert not intf.lsb_first + assert intf.select_high + assert intf.bits_per_word == 8 + intf.select_high = False + intf.lsb_first = True + intf.bits_per_word = 12 + assert not spidev.return_value.cshigh + assert spidev.return_value.lsbfirst + assert spidev.return_value.bits_per_word == 12 + intf.rate = 1000000 + assert intf.rate == 1000000 + intf.rate = 500000 + assert intf.rate == 500000 + + +# XXX Test two simultaneous SPI devices sharing clock, MOSI, and MISO, with +# separate select pins (including threaded tests which attempt simultaneous +# reading/writing) diff --git a/tests/test_spi_devices.py b/tests/test_spi_devices.py new file mode 100644 index 000000000..35b4d8fad --- /dev/null +++ b/tests/test_spi_devices.py @@ -0,0 +1,483 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2019 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import pytest +from collections import namedtuple +from math import isclose + +from gpiozero.pins.mock import MockSPIDevice, MockPin +from gpiozero import * + + +def clamp(v, min_value, max_value): + return min(max_value, max(min_value, v)) + +def scale(v, ref, bits): + v /= ref + vmin = -(2 ** bits) + vmax = -vmin - 1 + vrange = vmax - vmin + return int(((v + 1) / 2.0) * vrange + vmin) + + +class MockMCP3xxx(MockSPIDevice): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + channels=8, bits=10, pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, + pin_factory=pin_factory) + self.vref = 3.3 + self.channels = [0.0] * channels + self.channel_bits = 3 + self.bits = bits + self.state = 'idle' + + def on_start(self): + super().on_start() + self.state = 'idle' + + def on_bit(self): + if self.state == 'idle': + if self.rx_buf[-1]: + self.state = 'mode' + self.rx_buf = [] + elif self.state == 'mode': + if self.rx_buf[-1]: + self.state = 'single' + else: + self.state = 'diff' + self.rx_buf = [] + elif self.state in ('single', 'diff'): + if len(self.rx_buf) == self.channel_bits: + self.on_result(self.state == 'diff', self.rx_word()) + self.state = 'result' + elif self.state == 'result': + if not self.tx_buf: + self.state = 'idle' + self.rx_buf = [] + else: + assert False + + def on_result(self, differential, channel): + if differential: + pos_channel = channel + neg_channel = pos_channel ^ 1 + result = self.channels[pos_channel] - self.channels[neg_channel] + result = clamp(result, 0, self.vref) + else: + result = clamp(self.channels[channel], 0, self.vref) + result = scale(result, self.vref, self.bits) + self.tx_word(result, self.bits + 2) + + +class MockMCP3xx1(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + bits=10, pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=2, + bits=bits, pin_factory=pin_factory) + + def on_start(self): + super().on_start() + result = self.channels[0] - self.channels[1] + result = clamp(result, 0, self.vref) + result = scale(result, self.vref, self.bits) + self.tx_word(result, self.bits + 3) + + def on_bit(self): + pass + + +class MockMCP3xx2(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + bits=10, pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=2, + bits=bits, pin_factory=pin_factory) + self.channel_bits = 1 + + +class MockMCP33xx(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + channels=8, pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, + channels=channels, bits=12, pin_factory=pin_factory) + + def on_result(self, differential, channel): + if differential: + pos_channel = channel + neg_channel = pos_channel ^ 1 + result = self.channels[pos_channel] - self.channels[neg_channel] + result = clamp(result, -self.vref, self.vref) + else: + result = clamp(self.channels[channel], 0, self.vref) + result = scale(result, self.vref, self.bits) + if result < 0: + result += 8192 + self.tx_word(result, self.bits + 3) + + +class MockMCP3001(MockMCP3xx1): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, bits=10, + pin_factory=pin_factory) + + +class MockMCP3002(MockMCP3xx2): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, bits=10, + pin_factory=pin_factory) + + +class MockMCP3004(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=4, + bits=10, pin_factory=pin_factory) + + +class MockMCP3008(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=8, + bits=10, pin_factory=pin_factory) + + +class MockMCP3201(MockMCP3xx1): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, bits=12, + pin_factory=pin_factory) + + +class MockMCP3202(MockMCP3xx2): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, bits=12, + pin_factory=pin_factory) + + +class MockMCP3204(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=4, + bits=12, pin_factory=pin_factory) + + +class MockMCP3208(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=8, + bits=12, pin_factory=pin_factory) + + +class MockMCP3301(MockMCP3xxx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=2, + bits=12, pin_factory=pin_factory) + + def on_start(self): + super().on_start() + result = self.channels[0] - self.channels[1] + result = clamp(result, -self.vref, self.vref) + result = scale(result, self.vref, self.bits) + if result < 0: + result += 8192 + self.tx_word(result, self.bits + 4) + + +class MockMCP3302(MockMCP33xx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=4, + pin_factory=pin_factory) + + +class MockMCP3304(MockMCP33xx): + def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, *, + pin_factory=None): + super().__init__(clock_pin, mosi_pin, miso_pin, select_pin, channels=8, + pin_factory=pin_factory) + + +def single_mcp_test(mock, pot, channel, bits): + scale = 2**bits + tolerance = 1 / scale + voltage_tolerance = pot.max_voltage / scale + mock.channels[channel] = 0.0 + assert pot.raw_value == 0 + assert isclose(pot.value, 0.0, abs_tol=tolerance) + assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) + mock.channels[channel] = mock.vref / 2 + assert pot.raw_value == (scale / 2) - 1 + assert isclose(pot.value, 0.5, abs_tol=tolerance) + assert isclose(pot.voltage, pot.max_voltage / 2, abs_tol=voltage_tolerance) + mock.channels[channel] = mock.vref + assert pot.raw_value == scale - 1 + assert isclose(pot.value, 1.0, abs_tol=tolerance) + assert isclose(pot.voltage, pot.max_voltage, abs_tol=voltage_tolerance) + + +def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False): + scale = 2**bits + tolerance = 1 / scale + voltage_tolerance = pot.max_voltage / scale + mock.channels[pos_channel] = 0.0 + mock.channels[neg_channel] = 0.0 + assert pot.raw_value == 0 + assert isclose(pot.value, 0.0, abs_tol=tolerance) + assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) + mock.channels[pos_channel] = mock.vref / 2 + assert pot.raw_value == (scale / 2) - 1 + assert isclose(pot.value, 0.5, abs_tol=tolerance) + assert isclose(pot.voltage, pot.max_voltage / 2, abs_tol=voltage_tolerance) + mock.channels[pos_channel] = mock.vref + assert pot.raw_value == scale - 1 + assert isclose(pot.value, 1.0, abs_tol=tolerance) + assert isclose(pot.voltage, pot.max_voltage, abs_tol=voltage_tolerance) + mock.channels[neg_channel] = mock.vref / 2 + assert pot.raw_value == (scale / 2) - 1 + assert isclose(pot.value, 0.5, abs_tol=tolerance) + assert isclose(pot.voltage, pot.max_voltage / 2, abs_tol=voltage_tolerance) + mock.channels[pos_channel] = mock.vref / 2 + assert pot.raw_value == 0 + assert isclose(pot.value, 0.0, abs_tol=tolerance) + assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) + mock.channels[pos_channel] = 0.0 + mock.channels[neg_channel] = mock.vref + if full: + assert pot.raw_value == -scale + assert isclose(pot.value, -1.0, abs_tol=tolerance) + assert isclose(pot.voltage, -pot.max_voltage, abs_tol=voltage_tolerance) + else: + assert pot.raw_value == 0 + assert isclose(pot.value, 0.0, abs_tol=tolerance) + assert isclose(pot.voltage, 0.0, abs_tol=voltage_tolerance) + + +def test_spi_software_read(mock_factory): + class SPISlave(MockSPIDevice): + def on_start(self): + super().on_start() + for i in range(10): + self.tx_word(i) + with SPISlave(11, 10, 9, 8) as slave, mock_factory.spi() as master: + assert master.read(3) == [0, 1, 2] + assert master.read(6) == [0, 1, 2, 3, 4, 5] + slave.clock_phase = True + master.clock_phase = True + assert master.read(3) == [0, 1, 2] + assert master.read(6) == [0, 1, 2, 3, 4, 5] + + +def test_spi_software_write(mock_factory): + with MockSPIDevice(11, 10, 9, 8) as test_device, mock_factory.spi() as master: + master.write([0]) + assert test_device.rx_word() == 0 + master.write([2, 0]) + # 0b 0000_0010 0000_0000 + assert test_device.rx_word() == 512 + master.write([0, 1, 1]) + # 0b 0000_0000 0000_0001 0000_0001 + assert test_device.rx_word() == 257 + + +def test_spi_software_write_lsb_first(mock_factory): + with MockSPIDevice(11, 10, 9, 8, lsb_first=True) as test_device, \ + mock_factory.spi() as master: + # lsb_first means the bit-strings above get reversed + master.write([0]) + assert test_device.rx_word() == 0 + master.write([2, 0]) + # 0b 0000_0000 0100_0000 + assert test_device.rx_word() == 64 + master.write([0, 1, 1]) + # 0b 1000_0000 1000_0000 0000_0000 + assert test_device.rx_word() == 8421376 + + +def test_spi_software_clock_mode(mock_factory): + with mock_factory.spi() as master: + assert master.clock_mode == 0 + assert not master.clock_polarity + assert not master.clock_phase + master.clock_polarity = False + assert master.clock_mode == 0 + master.clock_polarity = True + assert master.clock_mode == 2 + master.clock_phase = True + assert master.clock_mode == 3 + master.clock_mode = 0 + assert not master.clock_polarity + assert not master.clock_phase + with pytest.raises(ValueError): + master.clock_mode = 5 + + +def test_spi_software_attr(mock_factory): + with mock_factory.spi() as master: + assert not master.lsb_first + assert not master.select_high + assert master.bits_per_word == 8 + master.bits_per_word = 12 + assert master.bits_per_word == 12 + master.lsb_first = True + assert master.lsb_first + master.select_high = True + assert master.select_high + with pytest.raises(ValueError): + master.bits_per_word = 0 + + +def test_analog_input_device_bad_init(mock_factory): + with pytest.raises(InputDeviceError): + AnalogInputDevice(None) + with pytest.raises(InputDeviceError): + AnalogInputDevice(bits=None) + with pytest.raises(InputDeviceError): + AnalogInputDevice(8, 0) + with pytest.raises(InputDeviceError): + AnalogInputDevice(bits=8, max_voltage=-1) + + +def test_MCP3001(mock_factory): + mock = MockMCP3001(11, 10, 9, 8) + with MCP3001() as pot: + assert repr(pot).startswith('' + with MCP3001(max_voltage=5.0) as pot: + differential_mcp_test(mock, pot, 0, 1, 10) + + +def test_MCP3002(mock_factory): + mock = MockMCP3002(11, 10, 9, 8) + with pytest.raises(ValueError): + MCP3002(channel=5) + with MCP3002(channel=1) as pot: + assert repr(pot).startswith('' + with MCP3002(channel=1, max_voltage=5.0) as pot: + single_mcp_test(mock, pot, 1, 10) + with MCP3002(channel=1, differential=True) as pot: + differential_mcp_test(mock, pot, 1, 0, 10) + + +def test_MCP3004(mock_factory): + mock = MockMCP3004(11, 10, 9, 8) + with pytest.raises(ValueError): + MCP3004(channel=5) + with MCP3004(channel=3) as pot: + assert repr(pot).startswith(' +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, +) +str = type('') + +import warnings +from fractions import Fraction + +import pytest + +from gpiozero.exc import AmbiguousTone +from gpiozero.tones import Tone + + +@pytest.fixture +def A4(request): + return Tone.from_frequency(440.0) + +def test_tone_init(A4): + with warnings.catch_warnings(record=True) as w: + warnings.resetwarnings() + assert Tone(440) == A4 + assert Tone("A4") == A4 + assert Tone(Fraction(880, 2)) == A4 + assert len(w) == 0 + assert Tone(69) == A4 + assert len(w) == 1 + assert Tone(0) != A4 + assert len(w) == 1 + assert isinstance(w[0].message, AmbiguousTone) + assert Tone(frequency=440) == A4 + assert Tone(note="A4") == A4 + assert Tone(midi=69) == A4 + with pytest.raises(TypeError): + Tone() + with pytest.raises(TypeError): + Tone(foo=1) + with pytest.raises(TypeError): + Tone(frequency=440, midi=69) + with pytest.raises(TypeError): + Tone(440, midi=69) + +def test_tone_str(A4): + assert str(A4) == "A4" + assert str(A4.up()) == "A#4" + assert str(A4.down(12)) == "A3" + assert repr(A4) == "".format(note='A4') + assert repr(Tone(13000)) == '' + +def test_tone_from_frequency(A4): + assert Tone.from_frequency(440) == A4 + assert Tone.from_frequency(440.0) == A4 + with pytest.raises(ValueError): + Tone.from_frequency(-100.0) + with pytest.raises(ValueError): + Tone.from_frequency(30000.0) + +def test_tone_from_note(A4): + assert Tone.from_note(b"A4") == A4 + assert Tone.from_note("A4") == A4 + with pytest.raises(ValueError): + Tone.from_note("a4") + with pytest.raises(ValueError): + Tone.from_note("foo") + with pytest.raises(ValueError): + Tone.from_note(0) + +def test_tone_from_midi(A4): + assert Tone.from_midi(69) == A4 + with pytest.raises(ValueError): + Tone.from_midi(500) + with pytest.raises(ValueError): + Tone.from_midi(-1) + +def test_tone_frequency(A4): + assert A4.frequency == 440.0 + assert A4.up(12).frequency == 880.0 + assert A4.down(12).frequency == 220.0 + +def test_tone_midi(A4): + assert A4.midi == 69 + assert A4.up().midi == 70 + assert A4.down().midi == 68 + with pytest.raises(ValueError): + Tone.from_frequency(2).midi + with pytest.raises(ValueError): + Tone.from_frequency(15000).midi + +def test_tone_note(A4): + assert A4.note == "A4" + assert A4.up().note == "A#4" + assert A4.down().note == "G#4" + with pytest.raises(ValueError): + Tone.from_midi(8).note diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 000000000..b1c2eccf3 --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,402 @@ +# vim: set fileencoding=utf-8: +# +# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins +# +# Copyright (c) 2016-2023 Dave Jones +# Copyright (c) 2020 Fangchen Li +# Copyright (c) 2018-2019 Ben Nuttall +# Copyright (c) 2016-2019 Andrew Scheller +# +# SPDX-License-Identifier: BSD-3-Clause + +import pytest +from math import sin, cos, radians, isclose +from statistics import mean, median +from time import time, sleep +from itertools import islice + +from gpiozero import Device, LED, Button, Robot +from gpiozero.tools import * + + +epsilon = 0.01 # time to sleep after setting source before checking value + +def test_set_source_by_value(mock_factory): + with LED(2) as led, Button(3) as btn: + led.source_delay = 0 + assert not led.value + assert not btn.value + led.source = btn.values + sleep(epsilon) + assert not led.value + assert not btn.value + btn.pin.drive_low() + sleep(epsilon) + assert led.value + assert btn.value + +def test_set_source_by_device(mock_factory): + with LED(2) as led, Button(3) as btn: + led.source_delay = 0 + assert not led.value + assert not btn.value + led.source = btn + sleep(epsilon) + assert not led.value + assert not btn.value + btn.pin.drive_low() + sleep(epsilon) + assert led.value + assert btn.value + +def test_negated(mock_factory): + assert list(negated(())) == [] + assert list(negated((True, True, False, False))) == [False, False, True, True] + +def test_negated_source_by_value(mock_factory): + with LED(2) as led, Button(3) as btn: + led.source_delay = 0 + assert not led.value + assert not btn.value + led.source = negated(btn.values) + sleep(epsilon) + assert led.value + assert not btn.value + btn.pin.drive_low() + sleep(epsilon) + assert not led.value + assert btn.value + +def test_negated_source_by_device(mock_factory): + with LED(2) as led, Button(3) as btn: + led.source_delay = 0 + assert not led.value + assert not btn.value + led.source = negated(btn) + sleep(epsilon) + assert led.value + assert not btn.value + btn.pin.drive_low() + sleep(epsilon) + assert not led.value + assert btn.value + +def test_inverted(): + with pytest.raises(ValueError): + list(inverted((), 0, 0)) + with pytest.raises(ValueError): + list(inverted((), 1, 1)) + with pytest.raises(ValueError): + list(inverted((), 1, 0)) + assert list(inverted(())) == [] + assert list(inverted((1, 0, 0.1, 0.5))) == [0, 1, 0.9, 0.5] + assert list(inverted((1, 0, 0.1, 0.5), 0, 1)) == [0, 1, 0.9, 0.5] + assert list(inverted((-1, 0, -0.1, -0.5), -1, 0)) == [0, -1, -0.9, -0.5] + assert list(inverted((1, 0, 0.1, 0.5, -1, -0.1, -0.5), -1, 1)) == [-1, 0, -0.1, -0.5, 1, 0.1, 0.5] + assert list(inverted((2, 1, 1.1, 1.5), 1, 2)) == [1, 2, 1.9, 1.5] + +def test_scaled(): + with pytest.raises(ValueError): + list(scaled((), 0, 1, 0, 0)) + with pytest.raises(ValueError): + list(scaled((), 0, 1, 1, 1)) + with pytest.raises(ValueError): + list(scaled((), 0, 1, 1, 0)) + assert list(scaled((), 0, 1)) == [] + # no scale + assert list(scaled((0, 1, 0.5, 0.1), 0, 1)) == [0, 1, 0.5, 0.1] + assert list(scaled((0, 1, 0.5, 0.1), 0, 1, 0, 1)) == [0, 1, 0.5, 0.1] + # multiply by 2 + assert list(scaled((0, 1, 0.5, 0.1), 0, 2, 0, 1)) == [0, 2, 1, 0.2] + # add 1 + assert list(scaled((0, 1, 0.5, 0.1), 1, 2, 0, 1)) == [1, 2, 1.5, 1.1] + # multiply by 2 then add 1 + assert list(scaled((0, 1, 0.5, 0.1), 1, 3, 0, 1)) == [1, 3, 2, 1.2] + # add 1 then multiply by 2 + assert list(scaled((0, 1, 0.5, 0.1), 2, 4, 0, 1)) == [2, 4, 3, 2.2] + # invert + assert list(scaled((0, 1, 0.5, 0.1), 1, 0, 0, 1)) == [1, 0, 0.5, 0.9] + # multiply by -1 then subtract 1 + assert list(scaled((0, 1, 0.5, 0.1), -1, -2, 0, 1)) == [-1, -2, -1.5, -1.1] + # scale 0->1 to -1->+1 + assert list(scaled((0, 1, 0.5, 0.1), -1, 1)) == [-1, 1, 0.0, -0.8] + assert list(scaled((0, 1, 0.5, 0.1), -1, 1, 0, 1)) == [-1, 1, 0.0, -0.8] + # scale -1->+1 to 0->1 + assert list(scaled((-1, 1, 0.0, -0.5), 0, 1, -1, 1)) == [0, 1, 0.5, 0.25] + +def test_scaled_full(): + assert list(scaled_full((0, 0.5, 1, 0.5))) == [-1, 0, 1, 0] + +def test_scaled_half(): + assert list(scaled_half((-1, 0, 1, 0))) == [0, 0.5, 1, 0.5] + +def test_clamped(): + with pytest.raises(ValueError): + list(clamped((), 0, 0)) + with pytest.raises(ValueError): + list(clamped((), 1, 1)) + with pytest.raises(ValueError): + list(clamped((), 1, 0)) + assert list(clamped(())) == [] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2))) == [0, 0, 0, 0, 0.5, 1, 1] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), 0, 1)) == [0, 0, 0, 0, 0.5, 1, 1] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -1, 1)) == [-1, -1, -0.5, 0, 0.5, 1, 1] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -2, 2)) == [-2, -1, -0.5, 0, 0.5, 1, 2] + +def test_absoluted(mock_factory): + assert list(absoluted(())) == [] + assert list(absoluted((-2, -1, 0, 1, 2))) == [2, 1, 0, 1, 2] + +def test_quantized(): + with pytest.raises(ValueError): + list(quantized((), 0)) + with pytest.raises(ValueError): + list(quantized((), 4, 0, 0)) + with pytest.raises(ValueError): + list(quantized((), 4, 1, 1)) + with pytest.raises(ValueError): + list(quantized((), 4, 1, 0)) + assert list(quantized((), 4)) == [] + assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4)) == [ + 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] + assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4, 0, 1)) == [ + 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] + assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 5)) == [ + 0.0, 0.0, 0.2, 0.2, 0.4, 0.4, 0.6, 0.6, 0.8, 0.8, 1.0] + assert list(quantized((0, 0.25, 0.5, 0.75, 1.0, 1.5, 1.75, 2.0), 2, 0, 2)) == [ + 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0] + assert list(quantized((1, 1.25, 1.5, 1.75, 2.0, 2.5, 2.75, 3.0), 2, 1, 3)) == [ + 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0] + +def test_booleanized(): + with pytest.raises(ValueError): + list(booleanized((), 0, 0)) + with pytest.raises(ValueError): + list(booleanized((), 1, 1)) + with pytest.raises(ValueError): + list(booleanized((), 1, 0)) + with pytest.raises(ValueError): + list(booleanized((), 0, 0.5, -0.2)) + with pytest.raises(ValueError): + list(booleanized((), 0, 0.5, 0.5)) + assert list(booleanized((), 0, 0.5)) == [] + assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 0, 0.5)) == [ + True, True, True, True, True, True, False, False, False, False, False] + assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75)) == [ + False, False, False, True, True, True, True, True, False, False, False, False] + assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75, 0.2)) == [ + False, False, False, False, False, True, True, True, True, True, False, False] + assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75)) == [ + False, False, False, True, True, True, True, True, False, False, False, False] + assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75, 0.2)) == [ + False, False, False, False, False, True, True, True, True, True, False, False] + +def test_all_values(): + assert list(all_values(())) == [] + assert list(all_values((False, True))) == [False, True] + assert list(all_values((0, 1, 0, 1), (0, 0, 0, 1))) == [0, 0, 0, 1] + +def test_any_values(): + assert list(any_values(())) == [] + assert list(any_values((False, True))) == [False, True] + assert list(any_values((0, 1, 0, 1), (0, 0, 0, 1))) == [0, 1, 0, 1] + +def test_averaged(): + assert list(averaged(())) == [] + assert list(averaged((0, 0.5, 1))) == [0, 0.5, 1] + assert list(averaged((0, 0.5, 1), (1, 1, 1))) == [0.5, 0.75, 1] + +def test_summed(): + assert list(summed(())) == [] + assert list(summed((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] + assert list(summed((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [1, 1, 1.5, 2] + +def test_multiplied(): + assert list(multiplied(())) == [] + assert list(multiplied((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] + assert list(multiplied((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [0, 0.25, 0.5, 1] + +def test_queued(): + with pytest.raises(ValueError): + list(queued((), 0)) + assert list(queued((), 5)) == [] + assert list(queued((1, 2, 3, 4, 5), 5)) == [1] + assert list(queued((1, 2, 3, 4, 5, 6), 5)) == [1, 2] + +def test_smoothed(): + with pytest.raises(ValueError): + list(smoothed((), 0)) + assert list(smoothed((), 5)) == [] + assert list(smoothed((1, 2, 3, 4, 5), 5)) == [3.0] + assert list(smoothed((1, 2, 3, 4, 5, 6), 5)) == [3.0, 4.0] + assert list(smoothed((1, 2, 3, 4, 5, 6), 5, average=mean)) == [3.0, 4.0] + assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=mean)) == [2.4, 3.2] + assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=median)) == [1, 4] + +def test_pre_delayed(): + with pytest.raises(ValueError): + list(pre_delayed((), -1)) + assert list(pre_delayed((), 0.01)) == [] + count = 0 + start = time() + for v in pre_delayed((0, 0, 0), 0.01): + count += 1 + assert v == 0 + assert time() - start >= 0.01 + start = time() + assert count == 3 + +def test_post_delayed(): + with pytest.raises(ValueError): + list(post_delayed((), -1)) + assert list(post_delayed((), 0.01)) == [] + count = 0 + start = time() + for v in post_delayed((1, 2, 2), 0.01): + count += 1 + if v == 1: + assert time() - start < 0.01 + else: + assert v == 2 + assert time() - start >= 0.01 + start = time() + assert time() - start >= 0.01 + assert count == 3 + +def test_pre_periodic_filtered(): + with pytest.raises(ValueError): + list(pre_periodic_filtered((), 2, -1)) + with pytest.raises(ValueError): + list(pre_periodic_filtered((), 0, 0)) + assert list(pre_periodic_filtered((), 2, 0)) == [] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 0)) == [3, 4, 5, 6, 7, 8, 9, 10] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [2, 4, 6, 8, 10] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [2, 3, 5, 6, 8, 9] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [3, 6, 9] + +def test_post_periodic_filtered(): + with pytest.raises(ValueError): + list(post_periodic_filtered((), 1, 0)) + with pytest.raises(ValueError): + list(post_periodic_filtered((), 0, 1)) + assert list(pre_periodic_filtered((), 1, 1)) == [] + assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [1, 3, 5, 7, 9] + assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [1, 4, 7, 10] + assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [1, 2, 4, 5, 7, 8, 10] + +def test_alternating_values(): + assert list(islice(alternating_values(), 5)) == [False, True, False, True, False] + assert list(islice(alternating_values(True), 5)) == [True, False, True, False, True] + +def test_random_values(): + for _, v in zip(range(1000), random_values()): + assert 0 <= v <= 1 + +def test_sin_values(): + for e, v in zip([0, 0], sin_values(2)): + assert -1 <= v <= 1 + assert isclose(e, v, abs_tol=1e-9) + for e, v in zip([0, 1, 0, -1], sin_values(4)): + assert -1 <= v <= 1 + assert isclose(e, v, abs_tol=1e-9) + for e, v in zip([0, 2**0.5/2, 1, 2**0.5/2, 0, -2**0.5/2, -1, -2**0.5/2], sin_values(8)): + assert -1 <= v <= 1 + assert isclose(e, v, abs_tol=1e-9) + firstval = None + for i, v in zip(range(1000), sin_values()): + assert -1 <= v <= 1 + assert isclose(v, sin(radians(i)), abs_tol=1e-9) + if i == 0: + firstval = v + else: + if i % 360 == 0: + assert v == firstval + for period in (360, 100): + firstval = None + for i, v in zip(range(1000), sin_values(period)): + assert -1 <= v <= 1 + if i == 0: + firstval = v + else: + if i % period == 0: + assert v == firstval + +def test_cos_values(): + for e, v in zip([1, -1], cos_values(2)): + assert -1 <= v <= 1 + assert isclose(e, v, abs_tol=1e-9) + for e, v in zip([1, 0, -1, 0], cos_values(4)): + assert -1 <= v <= 1 + assert isclose(e, v, abs_tol=1e-9) + for e, v in zip([1, 2**0.5/2, 0, -2**0.5/2, -1, -2**0.5/2, 0, 2**0.5/2], cos_values(8)): + assert -1 <= v <= 1 + assert isclose(e, v, abs_tol=1e-9) + firstval = None + for i, v in zip(range(1000), cos_values()): + assert -1 <= v <= 1 + assert isclose(v, cos(radians(i)), abs_tol=1e-9) + if i == 0: + firstval = v + else: + if i % 360 == 0: + assert v == firstval + for period in (360, 100): + firstval = None + for i, v in zip(range(1000), cos_values(period)): + assert -1 <= v <= 1 + if i == 0: + firstval = v + else: + if i % period == 0: + assert v == firstval + +def test_ramping_values(): + assert list(islice(ramping_values(2), 2)) == [0, 1] + assert list(islice(ramping_values(3), 5)) == [0, 2/3, 2/3, 0, 2/3] + assert list(islice(ramping_values(4), 4)) == [0, 0.5, 1, 0.5] + assert list(islice(ramping_values(8), 8)) == [0, 0.25, 0.5, 0.75, 1, 0.75, 0.5, 0.25] + firstval = None + for i, v in zip(range(1000), ramping_values()): + assert 0 <= v <= 1 + if i == 0: + firstval = v + else: + if i % 360 == 0: + v == firstval + for period in (360, 100): + firstval = None + for i, v in zip(range(1000), ramping_values(period)): + assert 0 <= v <= 1 + if i == 0: + firstval = v + else: + if i % period == 0: + assert v == firstval + +def test_zip_values(mock_factory): + with Button(2) as btn1, Button(3) as btn2: + zv = zip_values(btn1, btn2) + assert next(zv) == (False, False) + btn1.pin.drive_low() + assert next(zv) == (True, False) + btn2.pin.drive_low() + assert next(zv) == (True, True) + btn1.pin.drive_high() + assert next(zv) == (False, True) + btn2.pin.drive_high() + assert next(zv) == (False, False) + with Button(2) as btn1, Button(3) as btn2, Button(4) as btn3, Button(5) as btn4: + zv = zip_values(btn1, btn2, btn3, btn4) + assert next(zv) == (False, False, False, False) + btn1.pin.drive_low() + btn3.pin.drive_low() + assert next(zv) == (True, False, True, False) + btn2.pin.drive_low() + btn4.pin.drive_low() + assert next(zv) == (True, True, True, True) + btn1.pin.drive_high() + btn2.pin.drive_high() + btn3.pin.drive_high() + btn4.pin.drive_high() + sleep(epsilon) + assert next(zv) == (False, False, False, False) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..071773dcb --- /dev/null +++ b/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = {py39,py310,py311,py312} + +[testenv] +deps = .[test] +usedevelop = true +commands = make test +whitelist_externals = make +setenv = + COVERAGE_FILE=.coverage.{envname} + GPIOZERO_TEST_LOCK={toxworkdir}/real_pins_lock +passenv = GPIOZERO_* COVERAGE_* pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy