├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE.md ├── README.rst ├── docs ├── Makefile ├── _static │ └── .keep ├── api.rst ├── changelog.rst ├── conf.py ├── contributing.rst ├── developing.rst ├── examples │ ├── button_function.py │ ├── button_is_pressed.py │ ├── button_led.py │ ├── buzzer.py │ ├── led_blink.py │ ├── led_brightness.py │ ├── led_on_off.py │ ├── led_pulse.py │ ├── led_pulse_method.py │ ├── led_toggle.py │ ├── motor_move.py │ ├── pico_led.py │ ├── pico_temperature.py │ ├── pot_led.py │ ├── potentiometer.py │ ├── print_pinout.py │ ├── rgb_blink.py │ ├── rgb_cycle.py │ ├── rgb_led.py │ ├── rgb_pulse.py │ ├── rgb_toggle_invert.py │ ├── robot_rover_forward.py │ ├── robot_rover_square.py │ ├── servo_move.py │ ├── servo_pulse.py │ ├── servo_sweep.py │ ├── speaker.py │ ├── speaker_midi_notes.py │ ├── speaker_notes.py │ ├── speaker_tune.py │ └── ultrasonic_distance_sensor.py ├── gettingstarted.rst ├── images │ ├── distance_sensor_bb.svg │ ├── pico_led.png │ ├── pico_led.svg │ ├── pico_led_14_bb.svg │ ├── pico_led_14_bb_f.svg │ ├── robot_bb.svg │ ├── run-current-script.jpg │ ├── save-this-computer.png │ ├── save-this-raspberry-pi-pico.png │ ├── servo.svg │ ├── thonny-copy-picozero.jpg │ ├── thonny-install-package.jpg │ ├── thonny-manage-packages.jpg │ ├── thonny-navigate-downloads.jpg │ ├── thonny-packages-picozero.jpg │ ├── thonny-switch-interpreter.jpg │ ├── thonny-upload-files.jpg │ └── thonny-view-files.jpg ├── index.rst ├── make.bat ├── recipes.rst └── sketches │ ├── distance_sensor.fzz │ ├── pico_led_14.fzz │ ├── robot.fzz │ └── servo.fzz ├── package.json ├── picozero ├── __init__.py └── picozero.py ├── setup.py └── tests ├── README.rst └── test_picozero.py /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Each line is a file pattern followed by one or more owners. 2 | 3 | # These owners will be the default owners for everything in 4 | # the repo, unless a later match takes precedence. 5 | # They will be requested for review when a pull request is opened. 6 | 7 | # Teams: 8 | # * @raspberrypifoundation/digital-products-team @raspberrypifoundation/pico-zero 9 | 10 | # Individuals: 11 | * @grega @MarcScott 12 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | web@raspberrypi.org. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions to the picozero are welcome. Here is some advice. 4 | 5 | 1. picozero is 'lightweight', it is designed to be easy to use but also needs to run on a micro controller, please take this into account when considering feature requests or raising issues. 6 | 2. see below for other stuff! 7 | 8 | # Status 9 | 10 | picozero is currently in Beta, pre release 1.0, you should consider the following: 11 | 12 | - The API is not yet set, however, this doesn't mean that backwards compatibility is not important! It is a balancing act. 13 | - Requests for new features will need to be prioritised and responses to feature requests may take some time. 14 | - Refactoring of the code base is very likely and as a result pull requests may need rework. 15 | - Issues are likely to exist within the code base, be kind! 16 | 17 | # Suggestions 18 | 19 | If you have an idea for a new feature or would like to see a device included in picozero, please raise an [issue](https://github.com/RaspberryPiFoundation/picozero/issues). Please explain your reasoning clearly. 20 | 21 | # Bugs 22 | 23 | Please raise an [issue](https://github.com/RaspberryPiFoundation/picozero/issues) for any bugs found. Please include code examples and circuit diagrams if appropriate. 24 | 25 | # Pull requests 26 | 27 | All pull requests should be based on the [dev](https://github.com/RaspberryPiFoundation/picozero/tree/dev) branch of picozero. 28 | 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behaviour: 15 | 1. Setup this device like this '...' 16 | 2. Run this code '....' 17 | 3. This thing occurred '...' 18 | 4. This error was reported '...' 19 | 20 | Please include code in code blocks e.g. 21 | 22 | ```python 23 | print("picozero") 24 | ``` 25 | 26 | **Expected behaviour** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **System information:** 30 | - OS: [e.g. Windows] 31 | - Development environment [e.g. Thonny] 32 | - picozero version: [available in `/Lib/picozero/__init__.py`] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this picozero 4 | title: '' 5 | labels: nice to have 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .pytest_cache 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | .venv/ 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 95 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 96 | 97 | # User-specific stuff: 98 | .idea/guizero.iml 99 | .idea/misc.xml 100 | .idea/modules.xml 101 | .idea/vcs.xml 102 | .idea/workspace.xml 103 | .idea/tasks.xml 104 | 105 | # Sensitive or high-churn files: 106 | .idea/dataSources/ 107 | .idea/dataSources.ids 108 | .idea/dataSources.xml 109 | .idea/dataSources.local.xml 110 | .idea/sqlDataSources.xml 111 | .idea/dynamic.xml 112 | .idea/uiDesigner.xml 113 | 114 | # Gradle: 115 | .idea/gradle.xml 116 | .idea/libraries 117 | 118 | # Mongo Explorer plugin: 119 | .idea/mongoSettings.xml 120 | 121 | ## File-based project format: 122 | *.iws 123 | 124 | ## Plugin-specific files: 125 | 126 | # IntelliJ 127 | /out/ 128 | 129 | # mpeltonen/sbt-idea plugin 130 | .idea_modules/ 131 | 132 | # JIRA plugin 133 | atlassian-ide-plugin.xml 134 | 135 | # Crashlytics plugin (for Android Studio and IntelliJ) 136 | com_crashlytics_export_strings.xml 137 | crashlytics.properties 138 | crashlytics-build.properties 139 | fabric.properties 140 | 141 | # Editor detritus 142 | *.vim 143 | *.swp 144 | tags 145 | .vscode 146 | .idea 147 | 148 | # MKDocs build 149 | site/ 150 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2023 Raspberry Pi Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | picozero 2 | ======== 3 | 4 | |pypibadge| |docsbadge| 5 | 6 | A beginner-friendly library to help you use common electronics components with the Raspberry Pi Pico. 7 | 8 | .. code-block:: python 9 | 10 | from picozero import LED, Button 11 | 12 | led = LED(1) 13 | button = Button(2) 14 | 15 | button.when_pressed = led.on 16 | button.when_released = led.off 17 | 18 | Status 19 | ------ 20 | 21 | Beta. There will be bugs and issues. API changes are likely. More devices will be added over time. 22 | 23 | Documentation 24 | ------------- 25 | 26 | Documentation is available at `picozero.readthedocs.io `_: 27 | 28 | - `Installation and getting started guide `_ 29 | - `Recipes and how-to's `_ 30 | - `API `_ 31 | - `Example code `_ 32 | 33 | Code 34 | ---- 35 | 36 | The code and project is at `github.com/RaspberryPiFoundation/picozero `_. 37 | 38 | Issues can be raised at `github.com/RaspberryPiFoundation/picozero/issues `_ (see `Contributing `_). 39 | 40 | The latest distribution is available at `pypi.org/project/picozero/ `_. 41 | 42 | Thanks 43 | ------ 44 | 45 | picozero is inspired by `gpiozero `_ (and reuses some of its underlying structure), but is, by design, lighter weight and aligned with the Raspberry Pi Pico. Thank you to everyone who has contributed to the gpiozero project. 46 | 47 | .. |pypibadge| image:: https://badge.fury.io/py/picozero.svg 48 | :target: https://badge.fury.io/py/picozero 49 | :alt: Latest Version 50 | 51 | .. |docsbadge| image:: https://readthedocs.org/projects/picozero/badge/ 52 | :target: https://readthedocs.org/projects/picozero/ 53 | :alt: Docs 54 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/_static/.keep -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | picozero API 2 | ============ 3 | 4 | .. module:: picozero 5 | 6 | LED 7 | --- 8 | 9 | .. autofunction:: LED 10 | 11 | DigitalLED 12 | ---------- 13 | 14 | .. autoclass:: DigitalLED 15 | :show-inheritance: 16 | :inherited-members: 17 | :members: 18 | 19 | PWMLED 20 | ---------- 21 | 22 | .. autoclass:: PWMLED 23 | :show-inheritance: 24 | :inherited-members: 25 | :members: 26 | 27 | RGBLED 28 | ---------- 29 | 30 | .. autoclass:: RGBLED 31 | :show-inheritance: 32 | :inherited-members: 33 | :members: 34 | 35 | Buzzer 36 | ------ 37 | 38 | .. autoclass:: Buzzer 39 | :show-inheritance: 40 | :inherited-members: 41 | :members: 42 | 43 | PWMBuzzer 44 | --------- 45 | 46 | .. autoclass:: PWMBuzzer 47 | :show-inheritance: 48 | :inherited-members: 49 | :members: 50 | 51 | Speaker 52 | ------- 53 | 54 | .. autoclass:: Speaker 55 | :show-inheritance: 56 | :inherited-members: 57 | :members: 58 | 59 | Servo 60 | ----- 61 | .. autoclass:: Servo 62 | :show-inheritance: 63 | :inherited-members: 64 | :members: 65 | 66 | Motor 67 | ----- 68 | 69 | .. autoclass:: Motor 70 | :show-inheritance: 71 | :inherited-members: 72 | :members: 73 | 74 | Robot / Rover 75 | ------------- 76 | 77 | .. autoclass:: Robot 78 | :show-inheritance: 79 | :inherited-members: 80 | :members: 81 | 82 | DigitalOutputDevice 83 | ------------------- 84 | .. autoclass:: DigitalOutputDevice 85 | :show-inheritance: 86 | :inherited-members: 87 | :members: 88 | 89 | PWMOutputDevice 90 | --------------- 91 | .. autoclass:: PWMOutputDevice 92 | :show-inheritance: 93 | :inherited-members: 94 | :members: 95 | 96 | Button 97 | ------ 98 | 99 | .. autoclass:: Button 100 | :show-inheritance: 101 | :inherited-members: 102 | :members: 103 | 104 | Switch 105 | ------ 106 | 107 | .. autoclass:: Switch 108 | :show-inheritance: 109 | :inherited-members: 110 | :members: 111 | 112 | Potentiometer / Pot 113 | ------------------- 114 | 115 | .. autoclass:: Potentiometer 116 | :show-inheritance: 117 | :inherited-members: 118 | :members: 119 | 120 | TemperatureSensor / TempSensor / Thermistor 121 | ------------------------------------------- 122 | 123 | .. autoclass:: TemperatureSensor 124 | :show-inheritance: 125 | :inherited-members: 126 | :members: 127 | 128 | DistanceSensor 129 | -------------- 130 | 131 | .. autoclass:: DistanceSensor 132 | :show-inheritance: 133 | :inherited-members: 134 | :members: 135 | 136 | DigitalInputDevice 137 | ------------------ 138 | 139 | .. autoclass:: DigitalInputDevice 140 | :show-inheritance: 141 | :inherited-members: 142 | :members: 143 | 144 | pinout 145 | ------ 146 | 147 | .. autofunction:: pinout -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Change log 2 | ========== 3 | 4 | .. currentmodule:: picozero 5 | 6 | 0.4.1 - 2022-12-22 7 | ------------------ 8 | 9 | + Introduced ``pinout()`` 10 | + Bug fix with ``DigitalInputDevice.when_deactivated`` decorator 11 | + Documentation tidy up and minor fixes 12 | 13 | 0.4.0 - 2022-11-18 14 | ------------------ 15 | 16 | + Introduced ``Servo`` class 17 | + Documentation fixes 18 | 19 | 0.3.0 - 2022-08-12 20 | ------------------ 21 | 22 | + Introduced ``Motor``, ``Robot``, and ``DistanceSensor`` classes. 23 | + Renamed ``LED`` factory ``use_pwm`` parameter to ``pwm`` to match other classes. **Note:** This is an API breaking change. 24 | + Resolved issue with ``RGBLED`` when not using ``pwm``. 25 | + Resolved issue where ``blink`` / ``pulse`` rates of ``0`` raised a traceback error. 26 | + Other minor bug fixes. 27 | + Documentation updates. 28 | 29 | 0.2.0 - 2022-06-29 30 | ------------------ 31 | 32 | + Pico W compatibility fix for onboard LED. 33 | 34 | 0.1.1 - 2022-06-08 35 | ------------------ 36 | 37 | + Minor fixes for bugs found during testing. 38 | + Small improvements to exception messages. 39 | + Added close methods to Speaker and PWMOutputDevice. 40 | + Added unit tests. 41 | + Added RGBLED.colour as an alias to RGBLED.color. 42 | 43 | 0.1.0 - 2022-04-08 44 | ------------------ 45 | 46 | + Beta release. 47 | + Documentation updates. 48 | + Minor bug fixes and refactoring. 49 | 50 | 0.0.2 - 2022-03-31 51 | ------------------ 52 | 53 | + Bug fixes and documentation updates. 54 | 55 | 0.0.1 - 2022-03-21 56 | ------------------ 57 | 58 | + Initial alpha release to test installation process. 59 | 60 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 17 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 18 | 19 | # Mock out certain modules while building documentation 20 | class Mock: 21 | __all__ = [] 22 | def __init__(self, *args, **kw): pass 23 | def __call__(self, *args, **kw): return Mock() 24 | def __mul__(self, other): return Mock() 25 | def __and__(self, other): return Mock() 26 | def __bool__(self): return False 27 | def __nonzero__(self): return False 28 | @classmethod 29 | def __getattr__(cls, name): 30 | if name in ('__file__', '__path__'): 31 | return '/dev/null' 32 | else: 33 | return Mock() 34 | 35 | sys.modules['machine'] = Mock() 36 | sys.modules['micropython'] = Mock() 37 | 38 | # add the ticks_ms function to time (as it is in micropython) 39 | import time 40 | setattr(time, 'ticks_ms', lambda x: None) 41 | setattr(time, 'ticks_us', lambda x: None) 42 | 43 | # -- Project information ----------------------------------------------------- 44 | 45 | project = 'picozero' 46 | copyright = '2022, Raspberry Pi Foundation' 47 | author = 'Raspberry Pi Foundation' 48 | 49 | # The full version, including alpha/beta/rc tags 50 | release = '0.4.1' 51 | 52 | 53 | # -- General configuration --------------------------------------------------- 54 | 55 | # Add any Sphinx extension module names here, as strings. They can be 56 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 57 | # ones. 58 | extensions = [ 59 | 'sphinx.ext.autodoc', 60 | 'sphinx.ext.viewcode', 61 | 'sphinx.ext.intersphinx' 62 | ] 63 | 64 | # Add any paths that contain templates here, relative to this directory. 65 | templates_path = ['_templates'] 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | # This pattern also affects html_static_path and html_extra_path. 70 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 71 | 72 | 73 | # -- Options for HTML output ------------------------------------------------- 74 | 75 | # The theme to use for HTML and HTML Help pages. See the documentation for 76 | # a list of builtin themes. 77 | # 78 | # The theme to use for HTML and HTML Help pages. See the documentation for 79 | # a list of builtin themes. 80 | # 81 | if on_rtd: 82 | html_theme = 'sphinx_rtd_theme' 83 | #html_theme_options = {} 84 | html_sidebars = { 85 | '**': [ 86 | 'globaltoc.html', 87 | 'relations.html', 88 | 'searchbox.html', 89 | ], 90 | } 91 | else: 92 | html_theme = 'alabaster' 93 | #html_theme_options = {} 94 | #html_sidebars = {} 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ['_static'] 100 | 101 | # -- Autodoc configuration ------------------------------------------------ 102 | 103 | autodoc_member_order = 'groupwise' 104 | autodoc_default_flags = ['members'] -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributions to picozero are welcome. Please keep in mind that picozero is 'lightweight'. It is designed to be easy to use but also needs to run on a microcontroller; please take this into account when considering feature requests or raising issues. 5 | 6 | For more details, please see the following advice. 7 | 8 | Status 9 | ------ 10 | 11 | As picozero is currently in Beta, pre-release 1.0, you should consider the following: 12 | 13 | - The API is not yet set, however, this doesn't mean that backwards compatibility is not important! It is a balancing act. 14 | - Requests for new features will need to be prioritised and responses to feature requests may take some time. 15 | - Refactoring of the code base is very likely and, as a result, pull requests may need rework. 16 | - Issues are likely to exist within the code base. Be kind! 17 | 18 | Suggestions 19 | ----------- 20 | 21 | If you have an idea for a new feature or would like to see a device included in picozero, please raise an `issue`_. Please explain your reasoning clearly. 22 | 23 | Bugs 24 | ---- 25 | 26 | Please raise an `issue`_ for any bugs found. Please include code examples and circuit diagrams if appropriate. 27 | 28 | Pull requests 29 | ------------- 30 | 31 | All pull requests should be based on the `dev `_ branch of picozero. 32 | 33 | .. _issue: https://github.com/RaspberryPiFoundation/picozero/issues 34 | -------------------------------------------------------------------------------- /docs/developing.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | Instructions on how build and deploy picozero. 5 | 6 | Pre-requisites 7 | -------------- 8 | 9 | To build and deploy picozero, you need to install the dependencies :: 10 | 11 | pip3 install twine sphinx 12 | 13 | Build 14 | ----- 15 | 16 | 1. Update version numbers in the ``setup.py``, ``picozero/__init__.py``, and ``docs/conf.py`` files 17 | 18 | 2. Add release to ``docs/changelog.rst`` 19 | 20 | 3. Run ``setup.py`` and create a source distribution :: 21 | 22 | python3 setup.py sdist 23 | 24 | 4. Upload to PyPI :: 25 | 26 | twine upload dist/* 27 | 28 | 5. Push all changes to ``master`` branch 29 | 30 | 6. Create a `release `_ in github and upload ``picozero-#-#-#.tar.gz`` source file to the release 31 | 32 | Documentation 33 | ------------- 34 | 35 | The documentation site is built using Sphinx. 36 | 37 | Install sphinx using :: 38 | 39 | pip3 install sphinx 40 | 41 | To test the documentation build, run the following command from the docs directory :: 42 | 43 | $ ./make html 44 | 45 | The website will be built in the directory docs/_build/html. 46 | 47 | Documentation can be viewed at `picozero.readthedocs.io`_ and is automatically built and deployed on push to github. 48 | 49 | .. _picozero.readthedocs.io: https://picozero.readthedocs.io 50 | 51 | Tests 52 | ----- 53 | 54 | The tests are designed to be run on a Raspberry Pi Pico. 55 | 56 | 1. Install the `picozero `_ package 57 | 58 | 2. Install the `micropython-unittest `_ package 59 | 60 | 3. Copy the ``test_picozero.py`` file to the Pico 61 | 62 | 4. Run the ``test_picozero.py`` file 63 | 64 | If a test fails, it is helpful to be able to see verbose error messages. To see error messages, you need to modify the ``lib/unittest.py`` file on the Pico. 65 | 66 | Locate the following code in the ``run_class`` function:: 67 | 68 | # Uncomment to investigate failure in detail 69 | #raise 70 | 71 | Uncomment ``raise``:: 72 | 73 | # Uncomment to investigate failure in detail 74 | raise 75 | -------------------------------------------------------------------------------- /docs/examples/button_function.py: -------------------------------------------------------------------------------- 1 | from picozero import Button, pico_led 2 | from time import sleep 3 | 4 | button = Button(18) 5 | 6 | def led_on_off(): 7 | pico_led.on() 8 | sleep(1) 9 | pico_led.off() 10 | 11 | button.when_pressed = led_on_off 12 | -------------------------------------------------------------------------------- /docs/examples/button_is_pressed.py: -------------------------------------------------------------------------------- 1 | from picozero import Button 2 | from time import sleep 3 | 4 | button = Button(18) 5 | 6 | while True: 7 | if button.is_pressed: 8 | print("Button is pressed") 9 | else: 10 | print("Button is not pressed") 11 | sleep(0.1) 12 | -------------------------------------------------------------------------------- /docs/examples/button_led.py: -------------------------------------------------------------------------------- 1 | from picozero import Button, pico_led 2 | 3 | button = Button(18) 4 | 5 | button.when_pressed = pico_led.on 6 | button.when_released = pico_led.off 7 | -------------------------------------------------------------------------------- /docs/examples/buzzer.py: -------------------------------------------------------------------------------- 1 | # Active Buzzer that plays a note when powered 2 | from time import sleep 3 | from picozero import Buzzer 4 | 5 | buzzer = Buzzer(10) 6 | 7 | buzzer.on() 8 | sleep(1) 9 | buzzer.off() 10 | sleep(1) 11 | 12 | buzzer.beep() 13 | sleep(4) 14 | buzzer.off() 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/examples/led_blink.py: -------------------------------------------------------------------------------- 1 | from picozero import LED 2 | 3 | led = LED(14) 4 | 5 | led.blink() -------------------------------------------------------------------------------- /docs/examples/led_brightness.py: -------------------------------------------------------------------------------- 1 | from picozero import LED 2 | from time import sleep 3 | 4 | led = LED(14) 5 | 6 | while True: 7 | led.brightness = 0 # off 8 | sleep(1) 9 | led.brightness = 0.5 # half brightness 10 | sleep(1) 11 | led.brightness = 1 # full brightness 12 | sleep(1) 13 | -------------------------------------------------------------------------------- /docs/examples/led_on_off.py: -------------------------------------------------------------------------------- 1 | from picozero import LED 2 | from time import sleep 3 | 4 | led = LED(14) 5 | 6 | led.on() 7 | sleep(1) 8 | led.off() 9 | -------------------------------------------------------------------------------- /docs/examples/led_pulse.py: -------------------------------------------------------------------------------- 1 | from picozero import LED 2 | from time import sleep 3 | from math import sin, radians 4 | 5 | led = LED(14) 6 | 7 | while True: 8 | for i in range(360): 9 | angle = radians(i) 10 | led.brightness = 0.5 + 0.5 * sin(angle) 11 | sleep(0.01) -------------------------------------------------------------------------------- /docs/examples/led_pulse_method.py: -------------------------------------------------------------------------------- 1 | from picozero import LED 2 | 3 | led = LED(14) 4 | 5 | led.pulse() -------------------------------------------------------------------------------- /docs/examples/led_toggle.py: -------------------------------------------------------------------------------- 1 | from picozero import LED 2 | from time import sleep 3 | 4 | led = LED(14) 5 | 6 | while True: 7 | led.toggle() 8 | sleep(1) 9 | 10 | -------------------------------------------------------------------------------- /docs/examples/motor_move.py: -------------------------------------------------------------------------------- 1 | from picozero import Motor 2 | from time import sleep 3 | 4 | motor = Motor(14, 15) 5 | 6 | motor.move() 7 | sleep(1) 8 | motor.stop() -------------------------------------------------------------------------------- /docs/examples/pico_led.py: -------------------------------------------------------------------------------- 1 | from picozero import pico_led 2 | 3 | pico_led.on() 4 | 5 | -------------------------------------------------------------------------------- /docs/examples/pico_temperature.py: -------------------------------------------------------------------------------- 1 | # Choose View -> Plotter in Thonny to see a graph of the results 2 | 3 | from picozero import pico_temp_sensor 4 | from time import sleep 5 | 6 | while True: 7 | print(pico_temp_sensor.temp) 8 | sleep(0.1) 9 | -------------------------------------------------------------------------------- /docs/examples/pot_led.py: -------------------------------------------------------------------------------- 1 | from picozero import Pot, LED 2 | 3 | # Potentiometer connected to GP26 (ADC0), GND and 3V 4 | # LED connected to GP0 5 | 6 | pot = Pot(26) 7 | led = LED(0) 8 | 9 | while True: 10 | led.value = pot.value -------------------------------------------------------------------------------- /docs/examples/potentiometer.py: -------------------------------------------------------------------------------- 1 | # Potentiometer connected to GP26 (ADC0), GND and 3V 2 | 3 | from time import sleep 4 | from picozero import Pot 5 | 6 | pot = Pot(26) 7 | 8 | while True: 9 | print(pot.value, pot.voltage) 10 | sleep(0.1) 11 | 12 | -------------------------------------------------------------------------------- /docs/examples/print_pinout.py: -------------------------------------------------------------------------------- 1 | from picozero import pinout 2 | 3 | pinout() -------------------------------------------------------------------------------- /docs/examples/rgb_blink.py: -------------------------------------------------------------------------------- 1 | from picozero import RGBLED 2 | from time import sleep 3 | 4 | rgb = RGBLED(1, 2, 3) 5 | 6 | rgb.blink() # does not wait 7 | sleep(6) 8 | rgb.off() 9 | sleep(1) 10 | 11 | # blink purple 2 seconds, off 0.5 seconds 12 | rgb.blink(on_times=(2, 0.5), colors=((1, 0, 1), (0, 0, 0)), wait=True, n=3) 13 | 14 | rgb.off() 15 | sleep(1) 16 | 17 | # blink red 1 second, green 0.5 seconds, blue 0.25 seconds 18 | rgb.blink((1, 0.5, 0.25), colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), wait=True, n=2) 19 | -------------------------------------------------------------------------------- /docs/examples/rgb_cycle.py: -------------------------------------------------------------------------------- 1 | from picozero import RGBLED 2 | from time import sleep 3 | 4 | rgb = RGBLED(1, 2, 3) 5 | 6 | # Gradually colour cycle through colours between red and green, green and blue then blue and red 7 | rgb.cycle() 8 | sleep(4) 9 | rgb.off() 10 | sleep(1) 11 | 12 | # Colour cycle slower in the opposite direction 13 | rgb.cycle(fade_times=3, colors=((0, 0, 1), (0, 1, 0), (1, 0, 0)), wait=True, n=2) 14 | rgb.off() 15 | -------------------------------------------------------------------------------- /docs/examples/rgb_led.py: -------------------------------------------------------------------------------- 1 | from picozero import RGBLED 2 | from time import sleep 3 | 4 | rgb = RGBLED(red=2, green=1, blue=0) 5 | 6 | rgb.red = 255 # full red 7 | sleep(1) 8 | rgb.red = 128 # half red 9 | sleep(1) 10 | 11 | rgb.on() # white 12 | 13 | rgb.color = (0, 255, 0) # full green 14 | sleep(1) 15 | rgb.color = (255, 0, 255) # magenta 16 | sleep(1) 17 | rgb.color = (255, 255, 0) # yellow 18 | sleep(1) 19 | rgb.color = (0, 255, 255) # cyan 20 | sleep(1) 21 | rgb.color = (255, 255, 255) # white 22 | sleep(1) 23 | 24 | rgb.color = (0, 0, 0) # off 25 | sleep(1) 26 | 27 | # slowly increase intensity of blue 28 | for n in range(255): 29 | rgb.blue = n 30 | sleep(0.01) 31 | 32 | rgb.off() 33 | -------------------------------------------------------------------------------- /docs/examples/rgb_pulse.py: -------------------------------------------------------------------------------- 1 | from picozero import RGBLED 2 | from time import sleep 3 | 4 | rgb = RGBLED(1, 2, 3) 5 | 6 | rgb.pulse() # does not wait 7 | sleep(6) 8 | rgb.off() 9 | sleep(1) 10 | 11 | # 2 second to fade from purple to off, 0.5 seconds to change from off to purple 12 | rgb.pulse(fade_times=(2, 0.5), colors=((1, 0, 1), (0, 0, 0)), wait=True, n=3) 13 | 14 | rgb.off() 15 | sleep(1) 16 | 17 | # 4 seconds to change from red to green, 2 to change from green to blue, then 1 to change from blue back to red 18 | rgb.pulse((4, 2, 1), colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), wait=True, n=2) 19 | -------------------------------------------------------------------------------- /docs/examples/rgb_toggle_invert.py: -------------------------------------------------------------------------------- 1 | from picozero import RGBLED 2 | from time import sleep 3 | 4 | rgb = RGBLED(red=2, green=1, blue=0) 5 | 6 | rgb.color = (255, 165, 0) # orange 7 | sleep(1) 8 | 9 | for _ in range(6): 10 | rgb.toggle() 11 | sleep(1) 12 | 13 | for _ in range(6): 14 | rgb.invert() 15 | sleep(1) 16 | 17 | rgb.off() 18 | -------------------------------------------------------------------------------- /docs/examples/robot_rover_forward.py: -------------------------------------------------------------------------------- 1 | from picozero import Robot 2 | from time import sleep 3 | 4 | robot_rover = Robot(left=(14,15), right=(12,13)) 5 | 6 | # move forward 7 | robot_rover.forward() 8 | sleep(1) 9 | robot_rover.stop() 10 | -------------------------------------------------------------------------------- /docs/examples/robot_rover_square.py: -------------------------------------------------------------------------------- 1 | from picozero import Robot 2 | 3 | robot_rover = Robot(left=(14,15), right=(12,13)) 4 | 5 | for i in range(4): 6 | # move forward for 1 second 7 | robot_rover.forward(t=1, wait=True) 8 | # rotate to the left for 1 second 9 | robot_rover.left(t=1, wait=True) 10 | -------------------------------------------------------------------------------- /docs/examples/servo_move.py: -------------------------------------------------------------------------------- 1 | from picozero import Servo 2 | from time import sleep 3 | 4 | servo = Servo(1) 5 | 6 | servo.min() 7 | sleep(1) 8 | 9 | servo.mid() 10 | sleep(1) 11 | 12 | servo.max() 13 | sleep(1) 14 | 15 | servo.off() -------------------------------------------------------------------------------- /docs/examples/servo_pulse.py: -------------------------------------------------------------------------------- 1 | from picozero import Servo 2 | 3 | servo = Servo(1) 4 | 5 | servo.pulse() -------------------------------------------------------------------------------- /docs/examples/servo_sweep.py: -------------------------------------------------------------------------------- 1 | from picozero import Servo 2 | from time import sleep 3 | 4 | servo = Servo(1) 5 | 6 | for i in range(0, 100): 7 | servo.value = i / 100 8 | sleep(0.1) 9 | 10 | servo.off() 11 | -------------------------------------------------------------------------------- /docs/examples/speaker.py: -------------------------------------------------------------------------------- 1 | from picozero import Speaker 2 | from time import sleep 3 | 4 | speaker = Speaker(5) 5 | 6 | def tada(): 7 | c_note = 523 8 | speaker.play(c_note, 0.1) 9 | sleep(0.1) 10 | speaker.play(c_note, 0.9) 11 | 12 | def chirp(): 13 | global speaker 14 | for _ in range(5): 15 | for i in range(5000, 2999, -100): 16 | speaker.play(i, 0.01) 17 | sleep(0.2) 18 | 19 | 20 | try: 21 | tada() 22 | sleep(1) 23 | chirp() 24 | 25 | finally: # Turn the speaker off if interrupted 26 | speaker.off() 27 | -------------------------------------------------------------------------------- /docs/examples/speaker_midi_notes.py: -------------------------------------------------------------------------------- 1 | from picozero import Speaker 2 | from time import sleep 3 | 4 | speaker = Speaker(5) 5 | 6 | BEAT = 0.25 # 240 BPM 7 | 8 | dance = [[55, BEAT], ['', BEAT / 2], [55, BEAT], ['', BEAT / 2], [55, BEAT * 1.25], [55, BEAT], ['', BEAT / 2], [55, BEAT], 9 | [57, BEAT], ['', BEAT / 2], [57, BEAT], ['', BEAT / 2], [54, BEAT * 1.25], [54, BEAT], ['', BEAT / 2], [55, BEAT], 10 | [55, BEAT], [79, BEAT / 2], [55, BEAT], [79, BEAT / 2], [55, BEAT * 1.25], [55, BEAT], ['', BEAT / 2], [55, BEAT], 11 | [57, BEAT], [75, BEAT / 2], [57, BEAT], [75, BEAT / 2], [72, BEAT * 1.25], [56, BEAT], ['', BEAT / 2], [56, BEAT]] 12 | 13 | try: 14 | speaker.play(dance) 15 | 16 | finally: # Turn speaker off if interrupted 17 | speaker.off() 18 | -------------------------------------------------------------------------------- /docs/examples/speaker_notes.py: -------------------------------------------------------------------------------- 1 | from picozero import Speaker 2 | from time import sleep 3 | 4 | speaker = Speaker(5) 5 | 6 | BEAT = 0.4 7 | 8 | liten_mus = [ ['d5', BEAT / 2], ['d#5', BEAT / 2], ['f5', BEAT], ['d6', BEAT], ['a#5', BEAT], ['d5', BEAT], 9 | ['f5', BEAT], ['d#5', BEAT], ['d#5', BEAT], ['c5', BEAT / 2],['d5', BEAT / 2], ['d#5', BEAT], 10 | ['c6', BEAT], ['a5', BEAT], ['d5', BEAT], ['g5', BEAT], ['f5', BEAT], ['f5', BEAT], ['d5', BEAT / 2], 11 | ['d#5', BEAT / 2], ['f5', BEAT], ['g5', BEAT], ['a5', BEAT], ['a#5', BEAT], ['a5', BEAT], ['g5', BEAT], 12 | ['g5', BEAT], ['', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT / 2], ['d6', BEAT / 2], ['c6', BEAT / 2], 13 | ['a#5', BEAT / 2], ['a5', BEAT / 2], ['g5', BEAT / 2], ['a5', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT], 14 | ['f5', BEAT], ['f5', BEAT], ['f5', BEAT / 2], ['d#5', BEAT / 2], ['d5', BEAT], ['f5', BEAT], ['d6', BEAT], 15 | ['d6', BEAT / 2], ['c6', BEAT / 2], ['b5', BEAT], ['g5', BEAT], ['g5', BEAT], ['c6', BEAT / 2], 16 | ['a#5', BEAT / 2], ['a5', BEAT], ['f5', BEAT], ['d6', BEAT], ['a5', BEAT], ['a#5', BEAT * 1.5]] 17 | 18 | try: 19 | for note in liten_mus: 20 | speaker.play(note) 21 | sleep(0.1) # leave a gap between notes 22 | 23 | finally: # Turn speaker off if interrupted 24 | speaker.off() 25 | -------------------------------------------------------------------------------- /docs/examples/speaker_tune.py: -------------------------------------------------------------------------------- 1 | from picozero import Speaker 2 | 3 | speaker = Speaker(5) 4 | 5 | BEAT = 0.25 # 240 BPM 6 | 7 | liten_mus = [ ['d5', BEAT / 2], ['d#5', BEAT / 2], ['f5', BEAT], ['d6', BEAT], ['a#5', BEAT], ['d5', BEAT], 8 | ['f5', BEAT], ['d#5', BEAT], ['d#5', BEAT], ['c5', BEAT / 2],['d5', BEAT / 2], ['d#5', BEAT], 9 | ['c6', BEAT], ['a5', BEAT], ['d5', BEAT], ['g5', BEAT], ['f5', BEAT], ['f5', BEAT], ['d5', BEAT / 2], 10 | ['d#5', BEAT / 2], ['f5', BEAT], ['g5', BEAT], ['a5', BEAT], ['a#5', BEAT], ['a5', BEAT], ['g5', BEAT], 11 | ['g5', BEAT], ['', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT / 2], ['d6', BEAT / 2], ['c6', BEAT / 2], 12 | ['a#5', BEAT / 2], ['a5', BEAT / 2], ['g5', BEAT / 2], ['a5', BEAT / 2], ['a#5', BEAT / 2], ['c6', BEAT], 13 | ['f5', BEAT], ['f5', BEAT], ['f5', BEAT / 2], ['d#5', BEAT / 2], ['d5', BEAT], ['f5', BEAT], ['d6', BEAT], 14 | ['d6', BEAT / 2], ['c6', BEAT / 2], ['b5', BEAT], ['g5', BEAT], ['g5', BEAT], ['c6', BEAT / 2], 15 | ['a#5', BEAT / 2], ['a5', BEAT], ['f5', BEAT], ['d6', BEAT], ['a5', BEAT], ['a#5', BEAT * 1.5]] 16 | 17 | try: 18 | speaker.play(liten_mus) 19 | 20 | finally: # Turn speaker off if interrupted 21 | speaker.off() 22 | -------------------------------------------------------------------------------- /docs/examples/ultrasonic_distance_sensor.py: -------------------------------------------------------------------------------- 1 | from picozero import DistanceSensor 2 | from time import sleep 3 | 4 | ds = DistanceSensor(echo=2, trigger=3) 5 | 6 | while True: 7 | print(ds.distance) 8 | sleep(0.1) 9 | -------------------------------------------------------------------------------- /docs/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | .. picozero: a library for controlling Raspberry Pi Pico GPIO pins with MicroPython 2 | .. 3 | .. SPDX short identifier: MIT 4 | 5 | =============== 6 | Getting started 7 | =============== 8 | 9 | Install using Thonny 10 | ==================== 11 | 12 | Requirements 13 | ------------ 14 | 15 | A Windows, macOS, or Linux computer with the `Thonny Python IDE`_ installed. 16 | 17 | .. _Thonny Python IDE: https://thonny.org/ 18 | 19 | You can find information on how to install Thonny in the `Introduction to Raspberry Pi Pico guide`_. 20 | 21 | .. _Introduction to Raspberry Pi Pico guide: https://learning-admin.raspberrypi.org/en/projects/introduction-to-the-pico/2 22 | 23 | Once Thonny is installed, you will need to ensure that you are using the latest MicroPython firmware. Details on how to install or update the Raspberry Pi Pico MicroPython firmware can be found in the `Pico guide`_. 24 | 25 | .. _Pico guide: https://learning-admin.raspberrypi.org/en/projects/introduction-to-the-pico/3 26 | 27 | Select the MicroPython interpreter 28 | ---------------------------------- 29 | 30 | You can change which interpreter you are using in Thonny by selecting the desired option at the bottom right of the screen. Make sure that **MicroPython (Raspberry Pi Pico)** is selected. 31 | 32 | .. image:: images/thonny-switch-interpreter.jpg 33 | :alt: Selecting MicroPython (Raspberry Pi Pico) from the interpreter menu in the bottom right of the Thonny IDE 34 | 35 | Install picozero from PyPI in Thonny 36 | ------------------------------------ 37 | 38 | To install picozero within Thonny, select **Tools** > **Manage packages...** 39 | 40 | .. image:: images/thonny-manage-packages.jpg 41 | :alt: Selecting Manage Packages from the Tools menu in Thonny 42 | 43 | Search for `picozero` on PyPI. 44 | 45 | .. image:: images/thonny-packages-picozero.jpg 46 | :alt: picozero entered in the Search box of the Manage Packages window in Thonny 47 | 48 | Click on **install** to download the package. 49 | 50 | .. image:: images/thonny-install-package.jpg 51 | :alt: Information about the picozero package shown in the Manage Packages window 52 | 53 | Manual install 54 | ============== 55 | 56 | picozero can be installed by copying the ``picozero.py`` code to your Raspberry Pi Pico. 57 | 58 | Either clone the picozero `GitHub repository`_ or copy the code from the `picozero.py`_ file and save it on your main computer. 59 | 60 | .. _GitHub repository: https://github.com/RaspberryPiFoundation/picozero 61 | .. _picozero.py: https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/master/picozero/picozero.py?token=GHSAT0AAAAAABRLTKWZDBSYBE54NJ7AIZ6MYSENI2A 62 | 63 | Create a new file called picozero.py, copy code into the file and save it on your Raspberry Pi Pico. 64 | 65 | Copy picozero.py using Thonny 66 | ----------------------------- 67 | 68 | Alternatively, you can use the Thonny file manager to transfer the ``picozero.py`` file to your Raspberry Pi Pico. 69 | 70 | In the **View** menu, ensure that the **Files** option has a tick. This will let you see the files. 71 | 72 | .. image:: images/thonny-view-files.jpg 73 | :alt: The Files option selected from the View menu 74 | 75 | Either clone the picozero `GitHub repository`_ or copy the code from the `picozero.py`_ file and save it on your main computer. 76 | 77 | .. _GitHub repository: https://github.com/RaspberryPiFoundation/picozero 78 | .. _picozero.py: https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/master/picozero/picozero.py?token=GHSAT0AAAAAABRLTKWZDBSYBE54NJ7AIZ6MYSENI2A 79 | 80 | In Thonny, navigate to the cloned directory or location you saved the file in and find the ``picozero.py`` file. 81 | 82 | .. image:: images/thonny-navigate-downloads.jpg 83 | 84 | Right click on the file and select the **Upload to /** option. You should see a copy of the ``picozero.py`` file on the Raspberry Pi Pico. 85 | 86 | .. image:: images/thonny-upload-files.jpg 87 | :alt: The "Upload to /" option selected in the picozero.py file menu 88 | .. image:: images/thonny-copy-picozero.jpg 89 | :alt: The picozero.py file shown in the Raspberry Pi Pico file viewer. 90 | 91 | Write a program to control the onboard LED 92 | ========================================== 93 | 94 | The following code will blink the onboard LED at a frequency of once per second.:: 95 | 96 | from picozero import pico_led 97 | from time import sleep 98 | 99 | while True: 100 | pico_led.on() 101 | sleep(0.5) 102 | pico_led.off() 103 | sleep(0.5) 104 | 105 | Run the program on your computer 106 | -------------------------------- 107 | 108 | You can choose to run the program from your computer. 109 | 110 | Click on the **Run current script** button. 111 | 112 | .. image:: images/run-current-script.jpg 113 | 114 | Choose to save the script on **This computer** and provide a filename. 115 | 116 | .. image:: images/save-this-computer.png 117 | 118 | Run the program on your Raspberry Pi Pico 119 | ----------------------------------------- 120 | 121 | You can choose to run the program from the Raspberry Pi Pico. 122 | 123 | Click on the **Run current script** button. 124 | 125 | .. image:: images/run-current-script.jpg 126 | 127 | Choose to save the script on **Raspberry Pi Pico** and provide a filename. 128 | 129 | .. image:: images/save-this-raspberry-pi-pico.png 130 | 131 | If you call the file ``main.py``, it will run automatically when the Pico is powered on. 132 | -------------------------------------------------------------------------------- /docs/images/pico_led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/pico_led.png -------------------------------------------------------------------------------- /docs/images/run-current-script.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/run-current-script.jpg -------------------------------------------------------------------------------- /docs/images/save-this-computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/save-this-computer.png -------------------------------------------------------------------------------- /docs/images/save-this-raspberry-pi-pico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/save-this-raspberry-pi-pico.png -------------------------------------------------------------------------------- /docs/images/thonny-copy-picozero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-copy-picozero.jpg -------------------------------------------------------------------------------- /docs/images/thonny-install-package.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-install-package.jpg -------------------------------------------------------------------------------- /docs/images/thonny-manage-packages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-manage-packages.jpg -------------------------------------------------------------------------------- /docs/images/thonny-navigate-downloads.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-navigate-downloads.jpg -------------------------------------------------------------------------------- /docs/images/thonny-packages-picozero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-packages-picozero.jpg -------------------------------------------------------------------------------- /docs/images/thonny-switch-interpreter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-switch-interpreter.jpg -------------------------------------------------------------------------------- /docs/images/thonny-upload-files.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-upload-files.jpg -------------------------------------------------------------------------------- /docs/images/thonny-view-files.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/images/thonny-view-files.jpg -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | Table of Contents 4 | ================= 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | gettingstarted 11 | recipes 12 | api 13 | developing 14 | contributing 15 | changelog 16 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/recipes.rst: -------------------------------------------------------------------------------- 1 | Recipes 2 | ======= 3 | 4 | The recipes provide examples of how you can use picozero. 5 | 6 | Importing picozero 7 | ------------------- 8 | 9 | .. currentmodule:: picozero 10 | 11 | You will need add an `import` line to the top of your script to use picozero. 12 | 13 | You can import just what you need, separating items with a comma ``,``:: 14 | 15 | from picozero import pico_led, LED 16 | 17 | Now you can use :obj:`~picozero.pico_led` and :class:`~picozero.LED` in your script:: 18 | 19 | pico_led.on() # Turn on the LED on the Raspberry Pi Pico 20 | led = LED(14) # Control an LED connected to pin GP14 21 | led.on() 22 | 23 | Alternatively, the whole picozero library can be imported:: 24 | 25 | import picozero 26 | 27 | In this case, all references to picozero items must be prefixed:: 28 | 29 | picozero.pico_led.on() 30 | led = picozero.LED(14) 31 | 32 | 33 | Pico LED 34 | -------- 35 | 36 | .. image:: images/pico_led.svg 37 | :alt: A diagram of the Raspberry Pi Pico with a GP25 label attached to the onboard LED. 38 | 39 | To turn on the LED on your Raspberry Pi Pico: 40 | 41 | .. literalinclude:: examples/pico_led.py 42 | 43 | Run your script to see the LED turn on. 44 | 45 | Using the :obj:`pico_led` is equivalent to:: 46 | 47 | pico_led = LED(25) 48 | 49 | You can use :obj:`pico_led` in the same way as external LEDs created using :class:`LED`. 50 | 51 | Pin out 52 | ------- 53 | 54 | You can output a *diagram* of the Raspberry Pi Pico which displays its pins and their numbers. 55 | 56 | .. literalinclude:: examples/print_pinout.py 57 | 58 | :: 59 | 60 | ---usb--- 61 | GP0 1 |o o| -1 VBUS 62 | GP1 2 |o o| -2 VSYS 63 | GND 3 |o o| -3 GND 64 | GP2 4 |o o| -4 3V3_EN 65 | GP3 5 |o o| -5 3V3(OUT) 66 | GP4 6 |o o| -6 ADC_VREF 67 | GP5 7 |o o| -7 GP28 ADC2 68 | GND 8 |o o| -8 GND AGND 69 | GP6 9 |o o| -9 GP27 ADC1 70 | GP7 10 |o o| -10 GP26 ADC0 71 | GP8 11 |o o| -11 RUN 72 | GP9 12 |o o| -12 GP22 73 | GND 13 |o o| -13 GND 74 | GP10 14 |o o| -14 GP21 75 | GP11 15 |o o| -15 GP20 76 | GP12 16 |o o| -16 GP19 77 | GP13 17 |o o| -17 GP18 78 | GND 18 |o o| -18 GND 79 | GP14 19 |o o| -19 GP17 80 | GP15 20 |o o| -20 GP16 81 | --------- 82 | 83 | LEDs 84 | ------ 85 | 86 | .. image:: images/pico_led_14_bb.svg 87 | :alt: A diagram of the Raspberry Pi Pico with a yellow LED connected to GP14 and GND. 88 | 89 | You can control external LEDs with a Raspberry Pi Pico. 90 | 91 | Flash 92 | ~~~~~ 93 | 94 | Turn an :class:`LED` on and off: 95 | 96 | .. literalinclude:: examples/led_on_off.py 97 | 98 | Toggle an :class:`LED` to turn it from on to off or off to on: 99 | 100 | .. literalinclude:: examples/led_toggle.py 101 | 102 | Alternatively, you can use the :meth:`~picozero.LED.blink` method. 103 | 104 | .. literalinclude:: examples/led_blink.py 105 | 106 | Brightness 107 | ~~~~~~~~~~ 108 | 109 | Set the brightness of an :class:`LED`: 110 | 111 | .. literalinclude:: examples/led_brightness.py 112 | 113 | Create a pulse effect: 114 | 115 | .. literalinclude:: examples/led_pulse.py 116 | 117 | Alternatively, you can use the :meth:`~picozero.LED.pulse` method. 118 | 119 | .. literalinclude:: examples/led_pulse_method.py 120 | 121 | Buttons 122 | ------- 123 | 124 | You can connect buttons and switches to a Raspberry Pi Pico and detect when they are pressed. 125 | 126 | Check if a :class:`Button` is pressed: 127 | 128 | .. literalinclude:: examples/button_is_pressed.py 129 | 130 | 131 | Run a function every time a :class:`Button` is pressed: 132 | 133 | .. literalinclude:: examples/button_function.py 134 | 135 | .. note:: 136 | 137 | The line ``button.when_pressed = led_on_off`` does not run the 138 | function ``led_on_off``, rather it creates a reference to the function to be 139 | called when the button is pressed. Accidental use of ``button.when_pressed 140 | = led_on_off()`` would set the ``when_pressed`` action to :data:`None` (the 141 | return value of this function), which would mean nothing happens when the 142 | button is pressed. 143 | 144 | Turn the :obj:`pico_led` on when a :class:`Button` is pressed and off when it is released: 145 | 146 | .. literalinclude:: examples/button_led.py 147 | 148 | RGB LEDs 149 | -------- 150 | 151 | Set colours with an :class:`RGBLED`: 152 | 153 | .. literalinclude:: examples/rgb_led.py 154 | 155 | Use :meth:`~picozero.RGBLED.toggle` and :meth:`~picozero.RGBLED.invert`: 156 | 157 | .. literalinclude:: examples/rgb_toggle_invert.py 158 | 159 | Blink 160 | ~~~~~ 161 | 162 | Use :meth:`~picozero.RGBLED.blink` blink to change between colours. You can control which colours are used and how long the LED is set to each colour. The colour `(0, 0, 0)` represents off. 163 | 164 | You can control whether :meth:`~picozero.RGBLED.blink` runs a fixed number of times and whether it waits until it has finished or returns immediately so other code can run. 165 | 166 | .. literalinclude:: examples/rgb_blink.py 167 | 168 | Pulse 169 | ~~~~~ 170 | 171 | Use :meth:`~picozero.RGBLED.pulse` to gradually change the LED colour. The default will pulse between red and off, then green and off, then blue and off. 172 | 173 | .. literalinclude:: examples/rgb_pulse.py 174 | 175 | Cycle 176 | ~~~~~ 177 | 178 | The default for :meth:`~picozero.RGBLED.cycle` is to cycle from red to green, then green to blue, then blue to red. 179 | 180 | .. literalinclude:: examples/rgb_cycle.py 181 | 182 | Potentiometer 183 | ------------- 184 | 185 | Print the value, voltage, and percent reported by a potentiometer: 186 | 187 | .. literalinclude:: examples/potentiometer.py 188 | 189 | .. note:: 190 | 191 | In the Thonny Python editor, choose **View** > **Plotter** to plot the output of :meth:`print`. 192 | 193 | Use a potentiometer to control the brightness of an LED: 194 | 195 | .. literalinclude:: examples/pot_led.py 196 | 197 | Buzzer 198 | ------ 199 | 200 | Control an active buzzer that plays a note when powered: 201 | 202 | .. literalinclude:: examples/buzzer.py 203 | 204 | Speaker 205 | ------- 206 | 207 | Control a passive buzzer or speaker that can play different tones or frequencies: 208 | 209 | .. literalinclude:: examples/speaker.py 210 | 211 | Play a tune 212 | ~~~~~~~~~~~ 213 | 214 | Play a tune of note names and durations in beats: 215 | 216 | .. literalinclude:: examples/speaker_tune.py 217 | 218 | Play individual notes 219 | ~~~~~~~~~~~~~~~~~~~~~ 220 | 221 | Play individual notes and control the timing or perform another action: 222 | 223 | .. literalinclude:: examples/speaker_notes.py 224 | 225 | Servo 226 | ----- 227 | 228 | A servo motor connected to a single pin, 3.3v and ground. 229 | 230 | .. image:: images/servo.svg 231 | :alt: A diagram of the Raspberry Pi Pico connected to a servo motor 232 | 233 | Move the servo to its minimum, mid and maximum positions. 234 | 235 | .. literalinclude:: examples/servo_move.py 236 | 237 | Pulse the servo between its minumum and maximum position. 238 | 239 | .. literalinclude:: examples/servo_pulse.py 240 | 241 | Move the servo gradually from its minimum to maximum position in 100 increments. 242 | 243 | .. literalinclude:: examples/servo_sweep.py 244 | 245 | Motor 246 | ----- 247 | 248 | Move a motor connected via two pins (forward and backward) and a motor controller board: 249 | 250 | .. literalinclude:: examples/motor_move.py 251 | 252 | Robot rover 253 | ----------- 254 | 255 | Make a simple two-wheeled robot rover. 256 | 257 | .. image:: images/robot_bb.svg 258 | :alt: A diagram of the Raspberry Pi Pico connected to two motors via a motor controller board powered by a battery pack. 259 | 260 | Move the rover forward for 1 second and stop: 261 | 262 | .. literalinclude:: examples/robot_rover_forward.py 263 | 264 | Move the rover (roughly) in a square: 265 | 266 | .. literalinclude:: examples/robot_rover_square.py 267 | 268 | Internal temperature sensor 269 | --------------------------- 270 | 271 | Check the internal temperature of the Raspberry Pi Pico in degrees Celcius: 272 | 273 | .. literalinclude:: examples/pico_temperature.py 274 | 275 | Ultrasonic distance sensor 276 | -------------------------- 277 | 278 | Get the distance in metres from an ultrasonic distance sensor (HC-SR04): 279 | 280 | .. image:: images/distance_sensor_bb.svg 281 | :alt: A diagram of the Raspberry Pi Pico connected to an HC-SR04 distance sensor. 282 | 283 | .. literalinclude:: examples/ultrasonic_distance_sensor.py 284 | -------------------------------------------------------------------------------- /docs/sketches/distance_sensor.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/sketches/distance_sensor.fzz -------------------------------------------------------------------------------- /docs/sketches/pico_led_14.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/sketches/pico_led_14.fzz -------------------------------------------------------------------------------- /docs/sketches/robot.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/sketches/robot.fzz -------------------------------------------------------------------------------- /docs/sketches/servo.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaspberryPiFoundation/picozero/d63def75afa8fd191f4a2a18797237d29a832c66/docs/sketches/servo.fzz -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["picozero/__init__.py", "github:RaspberryPiFoundation/picozero/picozero/__init__.py"], 4 | ["picozero/picozero.py", "github:RaspberryPiFoundation/picozero/picozero/picozero.py"] 5 | ], 6 | "deps": [ 7 | ], 8 | "version": "0.4.2" 9 | } 10 | -------------------------------------------------------------------------------- /picozero/__init__.py: -------------------------------------------------------------------------------- 1 | __name__ = "picozero" 2 | __package__ = "picozero" 3 | __version__ = '0.4.1' 4 | __author__ = "Raspberry Pi Foundation" 5 | 6 | from .picozero import ( 7 | PWMChannelAlreadyInUse, 8 | EventFailedScheduleQueueFull, 9 | 10 | pinout, 11 | 12 | DigitalOutputDevice, 13 | DigitalLED, 14 | Buzzer, 15 | PWMOutputDevice, 16 | PWMLED, 17 | LED, 18 | pico_led, 19 | 20 | PWMBuzzer, 21 | Speaker, 22 | 23 | RGBLED, 24 | Motor, 25 | Robot, 26 | Servo, 27 | 28 | DigitalInputDevice, 29 | Switch, 30 | Button, 31 | 32 | AnalogInputDevice, 33 | Potentiometer, 34 | Pot, 35 | 36 | TemperatureSensor, 37 | pico_temp_sensor, 38 | TempSensor, 39 | Thermistor, 40 | 41 | DistanceSensor, 42 | ) 43 | -------------------------------------------------------------------------------- /picozero/picozero.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, PWM, Timer, ADC 2 | from micropython import schedule 3 | from time import ticks_ms, ticks_us, sleep 4 | 5 | ############################################################################### 6 | # EXCEPTIONS 7 | ############################################################################### 8 | 9 | class PWMChannelAlreadyInUse(Exception): 10 | pass 11 | 12 | class EventFailedScheduleQueueFull(Exception): 13 | pass 14 | 15 | ############################################################################### 16 | # SUPPORTING CLASSES 17 | ############################################################################### 18 | 19 | def clamp(n, low, high): return max(low, min(n, high)) 20 | 21 | def pinout(output=True): 22 | """ 23 | Returns a textual representation of the Raspberry Pi pico pins and functions. 24 | 25 | :param bool output: 26 | If :data:`True` (the default) the pinout will be "printed". 27 | 28 | """ 29 | pins = """ ---usb--- 30 | GP0 1 |o o| -1 VBUS 31 | GP1 2 |o o| -2 VSYS 32 | GND 3 |o o| -3 GND 33 | GP2 4 |o o| -4 3V3_EN 34 | GP3 5 |o o| -5 3V3(OUT) 35 | GP4 6 |o o| -6 ADC_VREF 36 | GP5 7 |o o| -7 GP28 ADC2 37 | GND 8 |o o| -8 GND AGND 38 | GP6 9 |o o| -9 GP27 ADC1 39 | GP7 10 |o o| -10 GP26 ADC0 40 | GP8 11 |o o| -11 RUN 41 | GP9 12 |o o| -12 GP22 42 | GND 13 |o o| -13 GND 43 | GP10 14 |o o| -14 GP21 44 | GP11 15 |o o| -15 GP20 45 | GP12 16 |o o| -16 GP19 46 | GP13 17 |o o| -17 GP18 47 | GND 18 |o o| -18 GND 48 | GP14 19 |o o| -19 GP17 49 | GP15 20 |o o| -20 GP16 50 | ---------""" 51 | 52 | if output: 53 | print(pins) 54 | return pins 55 | 56 | class PinMixin: 57 | """ 58 | Mixin used by devices that have a single pin number. 59 | """ 60 | 61 | @property 62 | def pin(self): 63 | """ 64 | Returns the pin number used by the device. 65 | """ 66 | return self._pin_num 67 | 68 | def __str__(self): 69 | return "{} (pin {})".format(self.__class__.__name__, self._pin_num) 70 | 71 | class PinsMixin: 72 | """ 73 | Mixin used by devices that use multiple pins. 74 | """ 75 | 76 | @property 77 | def pins(self): 78 | """ 79 | Returns a tuple of pins used by the device. 80 | """ 81 | return self._pin_nums 82 | 83 | def __str__(self): 84 | return "{} (pins - {})".format(self.__class__.__name__, self._pin_nums) 85 | 86 | class ValueChange: 87 | """ 88 | Internal class to control the value of an output device. 89 | 90 | :param OutputDevice output_device: 91 | The OutputDevice object you wish to change the value of. 92 | 93 | :param generator: 94 | A generator function that yields a 2d list of 95 | ((value, seconds), *). 96 | 97 | The output_device's value will be set for the number of 98 | seconds. 99 | 100 | :param int n: 101 | The number of times to repeat the sequence. If None, the 102 | sequence will repeat forever. 103 | 104 | :param bool wait: 105 | If True the ValueChange object will block (wait) until 106 | the sequence has completed. 107 | """ 108 | def __init__(self, output_device, generator, n, wait): 109 | self._output_device = output_device 110 | self._generator = generator 111 | self._n = n 112 | 113 | self._gen = self._generator() 114 | 115 | self._timer = Timer() 116 | self._running = True 117 | self._wait = wait 118 | 119 | self._set_value() 120 | 121 | def _set_value(self, timer_obj=None): 122 | if self._wait: 123 | # wait for the exection to end 124 | next_seq = self._get_value() 125 | while next_seq is not None: 126 | value, seconds = next_seq 127 | 128 | self._output_device._write(value) 129 | sleep(seconds) 130 | 131 | next_seq = self._get_value() 132 | 133 | else: 134 | # run the timer 135 | next_seq = self._get_value() 136 | if next_seq is not None: 137 | value, seconds = next_seq 138 | 139 | self._output_device._write(value) 140 | self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) 141 | 142 | if next_seq is None: 143 | # the sequence has finished, turn the device off 144 | self._output_device.off() 145 | self._running = False 146 | 147 | def _get_value(self): 148 | try: 149 | return next(self._gen) 150 | 151 | except StopIteration: 152 | 153 | self._n = self._n - 1 if self._n is not None else None 154 | if self._n == 0: 155 | # it's the end, return None 156 | return None 157 | else: 158 | # recreate the generator and start again 159 | self._gen = self._generator() 160 | return next(self._gen) 161 | 162 | def stop(self): 163 | """ 164 | Stops the ValueChange object running. 165 | """ 166 | self._running = False 167 | self._timer.deinit() 168 | 169 | ############################################################################### 170 | # OUTPUT DEVICES 171 | ############################################################################### 172 | 173 | class OutputDevice: 174 | """ 175 | Base class for output devices. 176 | """ 177 | def __init__(self, active_high=True, initial_value=False): 178 | self.active_high = active_high 179 | if initial_value is not None: 180 | self._write(initial_value) 181 | self._value_changer = None 182 | 183 | @property 184 | def active_high(self): 185 | """ 186 | Sets or returns the active_high property. If :data:`True`, the 187 | :meth:`on` method will set the Pin to HIGH. If :data:`False`, 188 | the :meth:`on` method will set the Pin to LOW (the :meth:`off` method 189 | always does the opposite). 190 | """ 191 | return self._active_state 192 | 193 | @active_high.setter 194 | def active_high(self, value): 195 | self._active_state = True if value else False 196 | self._inactive_state = False if value else True 197 | 198 | @property 199 | def value(self): 200 | """ 201 | Sets or returns a value representing the state of the device: 1 is on, 0 is off. 202 | """ 203 | return self._read() 204 | 205 | @value.setter 206 | def value(self, value): 207 | self._stop_change() 208 | self._write(value) 209 | 210 | def on(self, value=1, t=None, wait=False): 211 | """ 212 | Turns the device on. 213 | 214 | :param float value: 215 | The value to set when turning on. Defaults to 1. 216 | 217 | :param float t: 218 | The time in seconds that the device should be on. If None is 219 | specified, the device will stay on. The default is None. 220 | 221 | :param bool wait: 222 | If True, the method will block until the time `t` has expired. 223 | If False, the method will return and the device will turn on in 224 | the background. Defaults to False. Only effective if `t` is not 225 | None. 226 | """ 227 | if t is None: 228 | self.value = value 229 | else: 230 | self._start_change(lambda : iter([(value, t), ]), 1, wait) 231 | 232 | def off(self): 233 | """ 234 | Turns the device off. 235 | """ 236 | self.value = 0 237 | 238 | @property 239 | def is_active(self): 240 | """ 241 | Returns :data:`True` if the device is on. 242 | """ 243 | return bool(self.value) 244 | 245 | def toggle(self): 246 | """ 247 | If the device is off, turn it on. If it is on, turn it off. 248 | """ 249 | if self.is_active: 250 | self.off() 251 | else: 252 | self.on() 253 | 254 | def blink(self, on_time=1, off_time=None, n=None, wait=False): 255 | """ 256 | Makes the device turn on and off repeatedly. 257 | 258 | :param float on_time: 259 | The length of time in seconds that the device will be on. Defaults to 1. 260 | 261 | :param float off_time: 262 | The length of time in seconds that the device will be off. If `None`, 263 | it will be the same as ``on_time``. Defaults to `None`. 264 | 265 | :param int n: 266 | The number of times to repeat the blink operation. If None is 267 | specified, the device will continue blinking forever. The default 268 | is None. 269 | 270 | :param bool wait: 271 | If True, the method will block until the device stops turning on and off. 272 | If False, the method will return and the device will turn on and off in 273 | the background. Defaults to False. 274 | """ 275 | off_time = on_time if off_time is None else off_time 276 | 277 | self.off() 278 | 279 | # is there anything to change? 280 | if on_time > 0 or off_time > 0: 281 | self._start_change(lambda : iter([(1,on_time), (0,off_time)]), n, wait) 282 | 283 | def _start_change(self, generator, n, wait): 284 | self._value_changer = ValueChange(self, generator, n, wait) 285 | 286 | def _stop_change(self): 287 | if self._value_changer is not None: 288 | self._value_changer.stop() 289 | self._value_changer = None 290 | 291 | def close(self): 292 | """ 293 | Turns the device off. 294 | """ 295 | self.value = 0 296 | 297 | class DigitalOutputDevice(OutputDevice, PinMixin): 298 | """ 299 | Represents a device driven by a digital pin. 300 | 301 | :param int pin: 302 | The pin that the device is connected to. 303 | 304 | :param bool active_high: 305 | If :data:`True` (the default), the :meth:`on` method will set the Pin 306 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 307 | LOW (the :meth:`off` method always does the opposite). 308 | 309 | :param bool initial_value: 310 | If :data:`False` (the default), the LED will be off initially. If 311 | :data:`True`, the LED will be switched on initially. 312 | """ 313 | def __init__(self, pin, active_high=True, initial_value=False): 314 | self._pin_num = pin 315 | self._pin = Pin(pin, Pin.OUT) 316 | super().__init__(active_high, initial_value) 317 | 318 | def _value_to_state(self, value): 319 | return int(self._active_state if value else self._inactive_state) 320 | 321 | def _state_to_value(self, state): 322 | return int(bool(state) == self._active_state) 323 | 324 | def _read(self): 325 | return self._state_to_value(self._pin.value()) 326 | 327 | def _write(self, value): 328 | self._pin.value(self._value_to_state(value)) 329 | 330 | def close(self): 331 | """ 332 | Closes the device and turns the device off. Once closed, the device 333 | can no longer be used. 334 | """ 335 | super().close() 336 | self._pin = None 337 | 338 | class DigitalLED(DigitalOutputDevice): 339 | """ 340 | Represents a simple LED, which can be switched on and off. 341 | 342 | :param int pin: 343 | The pin that the device is connected to. 344 | 345 | :param bool active_high: 346 | If :data:`True` (the default), the :meth:`on` method will set the Pin 347 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 348 | LOW (the :meth:`off` method always does the opposite). 349 | 350 | :param bool initial_value: 351 | If :data:`False` (the default), the LED will be off initially. If 352 | :data:`True`, the LED will be switched on initially. 353 | """ 354 | pass 355 | 356 | DigitalLED.is_lit = DigitalLED.is_active 357 | 358 | class Buzzer(DigitalOutputDevice): 359 | """ 360 | Represents an active or passive buzzer, which can be turned on or off. 361 | 362 | :param int pin: 363 | The pin that the device is connected to. 364 | 365 | :param bool active_high: 366 | If :data:`True` (the default), the :meth:`on` method will set the Pin 367 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 368 | LOW (the :meth:`off` method always does the opposite). 369 | 370 | :param bool initial_value: 371 | If :data:`False` (the default), the Buzzer will be off initially. If 372 | :data:`True`, the Buzzer will be switched on initially. 373 | """ 374 | pass 375 | 376 | Buzzer.beep = Buzzer.blink 377 | 378 | class PWMOutputDevice(OutputDevice, PinMixin): 379 | """ 380 | Represents a device driven by a PWM pin. 381 | 382 | :param int pin: 383 | The pin that the device is connected to. 384 | 385 | :param int freq: 386 | The frequency of the PWM signal in hertz. Defaults to 100. 387 | 388 | :param int duty_factor: 389 | The duty factor of the PWM signal. This is a value between 0 and 65535. 390 | Defaults to 65535. 391 | 392 | :param bool active_high: 393 | If :data:`True` (the default), the :meth:`on` method will set the Pin 394 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 395 | LOW (the :meth:`off` method always does the opposite). 396 | 397 | :param bool initial_value: 398 | If :data:`False` (the default), the LED will be off initially. If 399 | :data:`True`, the LED will be switched on initially. 400 | """ 401 | 402 | PIN_TO_PWM_CHANNEL = ["0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B","7A","7B","0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B"] 403 | _channels_used = {} 404 | 405 | def __init__(self, pin, freq=100, duty_factor=65535, active_high=True, initial_value=False): 406 | self._check_pwm_channel(pin) 407 | self._pin_num = pin 408 | self._duty_factor = duty_factor 409 | self._pwm = PWM(Pin(pin)) 410 | self._pwm.freq(freq) 411 | super().__init__(active_high, initial_value) 412 | 413 | def _check_pwm_channel(self, pin_num): 414 | channel = PWMOutputDevice.PIN_TO_PWM_CHANNEL[pin_num] 415 | if channel in PWMOutputDevice._channels_used.keys(): 416 | raise PWMChannelAlreadyInUse( 417 | "PWM channel {} is already in use by {}. Use a different pin".format( 418 | channel, 419 | str(PWMOutputDevice._channels_used[channel]) 420 | ) 421 | ) 422 | else: 423 | PWMOutputDevice._channels_used[channel] = self 424 | 425 | def _state_to_value(self, state): 426 | return (state if self.active_high else self._duty_factor - state) / self._duty_factor 427 | 428 | def _value_to_state(self, value): 429 | return int(self._duty_factor * (value if self.active_high else 1 - value)) 430 | 431 | def _read(self): 432 | return self._state_to_value(self._pwm.duty_u16()) 433 | 434 | def _write(self, value): 435 | self._pwm.duty_u16(self._value_to_state(value)) 436 | 437 | @property 438 | def is_active(self): 439 | """ 440 | Returns :data:`True` if the device is on. 441 | """ 442 | return self.value != 0 443 | 444 | @property 445 | def freq(self): 446 | """ 447 | Returns the current frequency of the device. 448 | """ 449 | return self._pwm.freq() 450 | 451 | @freq.setter 452 | def freq(self, freq): 453 | """ 454 | Sets the frequency of the device. 455 | """ 456 | self._pwm.freq(freq) 457 | 458 | def blink(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): 459 | """ 460 | Makes the device turn on and off repeatedly. 461 | 462 | :param float on_time: 463 | The length of time in seconds the device will be on. Defaults to 1. 464 | 465 | :param float off_time: 466 | The length of time in seconds the device will be off. If `None`, 467 | it will be the same as ``on_time``. Defaults to `None`. 468 | 469 | :param int n: 470 | The number of times to repeat the blink operation. If `None`, the 471 | device will continue blinking forever. The default is `None`. 472 | 473 | :param bool wait: 474 | If True, the method will block until the LED stops blinking. If False, 475 | the method will return and the LED will blink in the background. 476 | Defaults to False. 477 | 478 | :param float fade_in_time: 479 | The length of time in seconds to spend fading in. Defaults to 0. 480 | 481 | :param float fade_out_time: 482 | The length of time in seconds to spend fading out. If `None`, 483 | it will be the same as ``fade_in_time``. Defaults to `None`. 484 | 485 | :param int fps: 486 | The frames per second that will be used to calculate the number of 487 | steps between off/on states when fading. Defaults to 25. 488 | """ 489 | self.off() 490 | 491 | off_time = on_time if off_time is None else off_time 492 | fade_out_time = fade_in_time if fade_out_time is None else fade_out_time 493 | 494 | def blink_generator(): 495 | if fade_in_time > 0: 496 | for s in [ 497 | (i * (1 / fps) / fade_in_time, 1 / fps) 498 | for i in range(int(fps * fade_in_time)) 499 | ]: 500 | yield s 501 | 502 | if on_time > 0: 503 | yield (1, on_time) 504 | 505 | if fade_out_time > 0: 506 | for s in [ 507 | (1 - (i * (1 / fps) / fade_out_time), 1 / fps) 508 | for i in range(int(fps * fade_out_time)) 509 | ]: 510 | yield s 511 | 512 | if off_time > 0: 513 | yield (0, off_time) 514 | 515 | # is there anything to change? 516 | if on_time > 0 or off_time > 0 or fade_in_time > 0 or fade_out_time > 0: 517 | self._start_change(blink_generator, n, wait) 518 | 519 | def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25): 520 | """ 521 | Makes the device pulse on and off repeatedly. 522 | 523 | :param float fade_in_time: 524 | The length of time in seconds that the device will take to turn on. 525 | Defaults to 1. 526 | 527 | :param float fade_out_time: 528 | The length of time in seconds that the device will take to turn off. 529 | Defaults to 1. 530 | 531 | :param int fps: 532 | The frames per second that will be used to calculate the number of 533 | steps between off/on states. Defaults to 25. 534 | 535 | :param int n: 536 | The number of times to pulse the LED. If None, the LED will pulse 537 | forever. Defaults to None. 538 | 539 | :param bool wait: 540 | If True, the method will block until the LED stops pulsing. If False, 541 | the method will return and the LED will pulse in the background. 542 | Defaults to False. 543 | """ 544 | self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, wait=wait, fps=fps) 545 | 546 | def close(self): 547 | """ 548 | Closes the device and turns the device off. Once closed, the device 549 | can no longer be used. 550 | """ 551 | super().close() 552 | del PWMOutputDevice._channels_used[ 553 | PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] 554 | ] 555 | self._pwm.deinit() 556 | self._pwm = None 557 | 558 | class PWMLED(PWMOutputDevice): 559 | """ 560 | Represents an LED driven by a PWM pin; the brightness of the LED can be changed. 561 | 562 | :param int pin: 563 | The pin that the device is connected to. 564 | 565 | :param int freq: 566 | The frequency of the PWM signal in hertz. Defaults to 100. 567 | 568 | :param int duty_factor: 569 | The duty factor of the PWM signal. This is a value between 0 and 65535. 570 | Defaults to 65535. 571 | 572 | :param bool active_high: 573 | If :data:`True` (the default), the :meth:`on` method will set the Pin 574 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 575 | LOW (the :meth:`off` method always does the opposite). 576 | 577 | :param bool initial_value: 578 | If :data:`False` (the default), the LED will be off initially. If 579 | :data:`True`, the LED will be switched on initially. 580 | """ 581 | PWMLED.brightness = PWMLED.value 582 | 583 | def LED(pin, pwm=True, active_high=True, initial_value=False): 584 | """ 585 | Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on 586 | the value of the `pwm` parameter. 587 | 588 | :: 589 | 590 | from picozero import LED 591 | 592 | my_pwm_led = LED(1) 593 | 594 | my_digital_led = LED(2, pwm=False) 595 | 596 | :param int pin: 597 | The pin that the device is connected to. 598 | 599 | :param int pin: 600 | If `pwm` is :data:`True` (the default), a :class:`PWMLED` will be 601 | returned. If `pwm` is :data:`False`, a :class:`DigitalLED` will be 602 | returned. A :class:`PWMLED` can control the brightness of the LED but 603 | uses 1 PWM channel. 604 | 605 | :param bool active_high: 606 | If :data:`True` (the default), the :meth:`on` method will set the Pin 607 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 608 | LOW (the :meth:`off` method always does the opposite). 609 | 610 | :param bool initial_value: 611 | If :data:`False` (the default), the device will be off initially. If 612 | :data:`True`, the device will be switched on initially. 613 | """ 614 | if pwm: 615 | return PWMLED( 616 | pin=pin, 617 | active_high=active_high, 618 | initial_value=initial_value) 619 | else: 620 | return DigitalLED( 621 | pin=pin, 622 | active_high=active_high, 623 | initial_value=initial_value) 624 | 625 | try: 626 | pico_led = LED("LED", pwm=False) 627 | except TypeError: 628 | # older version of micropython before "LED" was supported 629 | pico_led = LED(25, pwm=False) 630 | 631 | class PWMBuzzer(PWMOutputDevice): 632 | """ 633 | Represents a passive buzzer driven by a PWM pin; the volume of the buzzer can be changed. 634 | 635 | :param int pin: 636 | The pin that the buzzer is connected to. 637 | 638 | :param int freq: 639 | The frequency of the PWM signal in hertz. Defaults to 440. 640 | 641 | :param int duty_factor: 642 | The duty factor of the PWM signal. This is a value between 0 and 65535. 643 | Defaults to 1023. 644 | 645 | :param bool active_high: 646 | If :data:`True` (the default), the :meth:`on` method will set the Pin 647 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 648 | LOW (the :meth:`off` method always does the opposite). 649 | 650 | :param bool initial_value: 651 | If :data:`False` (the default), the buzzer will be off initially. If 652 | :data:`True`, the buzzer will be switched on initially. 653 | """ 654 | def __init__(self, pin, freq=440, duty_factor=1023, active_high=True, initial_value=False): 655 | super().__init__(pin, freq, duty_factor, active_high, initial_value) 656 | 657 | PWMBuzzer.volume = PWMBuzzer.value 658 | PWMBuzzer.beep = PWMBuzzer.blink 659 | 660 | class Speaker(OutputDevice, PinMixin): 661 | """ 662 | Represents a speaker driven by a PWM pin. 663 | 664 | :param int pin: 665 | The pin that the speaker is connected to. 666 | 667 | :param int initial_freq: 668 | The initial frequency of the PWM signal in hertz. Defaults to 440. 669 | 670 | :param int initial_volume: 671 | The initial volume of the PWM signal. This is a value between 0 and 672 | 1. Defaults to 0. 673 | 674 | :param int duty_factor: 675 | The duty factor of the PWM signal. This is a value between 0 and 65535. 676 | Defaults to 1023. 677 | 678 | :param bool active_high: 679 | If :data:`True` (the default), the :meth:`on` method will set the Pin 680 | to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to 681 | LOW (the :meth:`off` method always does the opposite). 682 | """ 683 | NOTES = { 684 | 'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, 685 | 'a#1': 58, 'b1': 62, 'c2': 65, 'c#2': 69, 'd2': 73, 'd#2': 78, 686 | 'e2': 82, 'f2': 87, 'f#2': 93, 'g2': 98, 'g#2': 104, 'a2': 110, 'a#2': 117, 'b2': 123, 687 | 'c3': 131, 'c#3': 139, 'd3': 147, 'd#3': 156, 'e3': 165, 'f3': 175, 'f#3': 185, 'g3': 196, 'g#3': 208, 'a3': 220, 'a#3': 233, 'b3': 247, 688 | 'c4': 262, 'c#4': 277, 'd4': 294, 'd#4': 311, 'e4': 330, 'f4': 349, 'f#4': 370, 'g4': 392, 'g#4': 415, 'a4': 440, 'a#4': 466, 'b4': 494, 689 | 'c5': 523, 'c#5': 554, 'd5': 587, 'd#5': 622, 'e5': 659, 'f5': 698, 'f#5': 740, 'g5': 784, 'g#5': 831, 'a5': 880, 'a#5': 932, 'b5': 988, 690 | 'c6': 1047, 'c#6': 1109, 'd6': 1175, 'd#6': 1245, 'e6': 1319, 'f6': 1397, 'f#6': 1480, 'g6': 1568, 'g#6': 1661, 'a6': 1760, 'a#6': 1865, 'b6': 1976, 691 | 'c7': 2093, 'c#7': 2217, 'd7': 2349, 'd#7': 2489, 692 | 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, 693 | 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 694 | } 695 | 696 | def __init__(self, pin, initial_freq=440, initial_volume=0, duty_factor=1023, active_high=True): 697 | 698 | self._pin_num = pin 699 | self._pwm_buzzer = PWMBuzzer( 700 | pin, 701 | freq=initial_freq, 702 | duty_factor=duty_factor, 703 | active_high=active_high, 704 | initial_value=None, 705 | ) 706 | 707 | super().__init__(active_high, None) 708 | self.volume = initial_volume 709 | 710 | def on(self, volume=1): 711 | self.volume = volume 712 | 713 | def off(self): 714 | self.volume = 0 715 | 716 | @property 717 | def value(self): 718 | """ 719 | Sets or returns the value of the speaker. The value is a tuple of (freq, volume). 720 | """ 721 | return tuple(self.freq, self.volume) 722 | 723 | @value.setter 724 | def value(self, value): 725 | self._stop_change() 726 | self._write(value) 727 | 728 | @property 729 | def volume(self): 730 | """ 731 | Sets or returns the volume of the speaker: 1 for maximum volume, 0 for off. 732 | """ 733 | return self._volume 734 | 735 | @volume.setter 736 | def volume(self, value): 737 | self._volume = value 738 | self.value = (self.freq, self.volume) 739 | 740 | @property 741 | def freq(self): 742 | """ 743 | Sets or returns the current frequency of the speaker. 744 | """ 745 | return self._pwm_buzzer.freq 746 | 747 | @freq.setter 748 | def freq(self, freq): 749 | self.value = (freq, self.volume) 750 | 751 | def _write(self, value): 752 | # set the frequency 753 | if value[0] is not None: 754 | self._pwm_buzzer.freq = value[0] 755 | 756 | # write the volume value 757 | if value[1] is not None: 758 | self._pwm_buzzer.volume = value[1] 759 | 760 | def _to_freq(self, freq): 761 | if freq is not None and freq != '' and freq != 0: 762 | if type(freq) is str: 763 | return int(self.NOTES[freq]) 764 | elif freq <= 128 and freq > 0: # MIDI 765 | midi_factor = 2**(1/12) 766 | return int(440 * midi_factor ** (freq - 69)) 767 | else: 768 | return freq 769 | else: 770 | return None 771 | 772 | def beep(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): 773 | """ 774 | Makes the buzzer turn on and off repeatedly. 775 | 776 | :param float on_time: 777 | The length of time in seconds that the device will be on. Defaults to 1. 778 | 779 | :param float off_time: 780 | The length of time in seconds that the device will be off. If `None`, 781 | it will be the same as ``on_time``. Defaults to `None`. 782 | 783 | :param int n: 784 | The number of times to repeat the beep operation. If `None`, the 785 | device will continue beeping forever. The default is `None`. 786 | 787 | :param bool wait: 788 | If True, the method will block until the buzzer stops beeping. If False, 789 | the method will return and the buzzer will beep in the background. 790 | Defaults to False. 791 | 792 | :param float fade_in_time: 793 | The length of time in seconds to spend fading in. Defaults to 0. 794 | 795 | :param float fade_out_time: 796 | The length of time in seconds to spend fading out. If `None`, 797 | it will be the same as ``fade_in_time``. Defaults to `None`. 798 | 799 | :param int fps: 800 | The frames per second that will be used to calculate the number of 801 | steps between off/on states when fading. Defaults to 25. 802 | """ 803 | self._pwm_buzzer.blink(on_time, off_time, n, wait, fade_in_time, fade_out_time, fps) 804 | 805 | def play(self, tune=440, duration=1, volume=1, n=1, wait=True): 806 | """ 807 | Plays a tune for a given duration. 808 | 809 | :param int tune: 810 | 811 | The tune to play can be specified as: 812 | 813 | + a single "note", represented as: 814 | + a frequency in Hz e.g. `440` 815 | + a midi note e.g. `60` 816 | + a note name as a string e.g. `"E4"` 817 | + a list of notes and duration e.g. `[440, 1]` or `["E4", 2]` 818 | + a list of two value tuples of (note, duration) e.g. `[(440,1), (60, 2), ("e4", 3)]` 819 | 820 | Defaults to `440`. 821 | 822 | :param int volume: 823 | The volume of the tune; 1 is maximum volume, 0 is mute. Defaults to 1. 824 | 825 | :param float duration: 826 | The duration of each note in seconds. Defaults to 1. 827 | 828 | :param int n: 829 | The number of times to play the tune. If None, the tune will play 830 | forever. Defaults to 1. 831 | 832 | :param bool wait: 833 | If True, the method will block until the tune has finished. If False, 834 | the method will return and the tune will play in the background. 835 | Defaults to True. 836 | """ 837 | 838 | self.off() 839 | 840 | # tune isn't a list, so it must be a single frequency or note 841 | if not isinstance(tune, (list, tuple)): 842 | tune = [(tune, duration)] 843 | # if the first element isn't a list, then it must be list of a single note and duration 844 | elif not isinstance(tune[0], (list, tuple)): 845 | tune = [tune] 846 | 847 | def tune_generator(): 848 | for note in tune: 849 | 850 | # note isn't a list or tuple, it must be a single frequency or note 851 | if not isinstance(note, (list, tuple)): 852 | # make it into a tuple 853 | note = (note, duration) 854 | 855 | # turn the notes into frequencies 856 | freq = self._to_freq(note[0]) 857 | freq_duration = note[1] 858 | freq_volume = volume if freq is not None else 0 859 | 860 | # if this is a tune of greater than 1 note, add gaps between notes 861 | if len(tune) == 1: 862 | yield ((freq, freq_volume), freq_duration) 863 | else: 864 | yield ((freq, freq_volume), freq_duration * 0.9) 865 | yield ((freq, 0), freq_duration * 0.1) 866 | 867 | self._start_change(tune_generator, n, wait) 868 | 869 | def close(self): 870 | self._pwm_buzzer.close() 871 | 872 | class RGBLED(OutputDevice, PinsMixin): 873 | """ 874 | Extends :class:`OutputDevice` and represents a full colour LED component (composed 875 | of red, green, and blue LEDs). 876 | Connect the common cathode (longest leg) to a ground pin; connect each of 877 | the other legs (representing the red, green, and blue anodes) to any GP 878 | pins. You should use three limiting resistors (one per anode). 879 | The following code will make the LED yellow:: 880 | 881 | from picozero import RGBLED 882 | rgb = RGBLED(1, 2, 3) 883 | rgb.color = (1, 1, 0) 884 | 885 | 0–255 colours are also supported:: 886 | 887 | rgb.color = (255, 255, 0) 888 | 889 | :type red: int 890 | :param red: 891 | The GP pin that controls the red component of the RGB LED. 892 | :type green: int 893 | :param green: 894 | The GP pin that controls the green component of the RGB LED. 895 | :type blue: int 896 | :param blue: 897 | The GP pin that controls the blue component of the RGB LED. 898 | :param bool active_high: 899 | Set to :data:`True` (the default) for common cathode RGB LEDs. If you 900 | are using a common anode RGB LED, set this to :data:`False`. 901 | :type initial_value: ~colorzero.Color or tuple 902 | :param initial_value: 903 | The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. 904 | :param bool pwm: 905 | If :data:`True` (the default), construct :class:`PWMLED` instances for 906 | each component of the RGBLED. If :data:`False`, construct 907 | :class:`DigitalLED` instances. 908 | 909 | """ 910 | def __init__(self, red=None, green=None, blue=None, active_high=True, 911 | initial_value=(0, 0, 0), pwm=True): 912 | self._pin_nums = (red, green, blue) 913 | self._leds = () 914 | self._last = initial_value 915 | LEDClass = PWMLED if pwm else DigitalLED 916 | self._leds = tuple( 917 | LEDClass(pin, active_high=active_high) 918 | for pin in (red, green, blue)) 919 | super().__init__(active_high, initial_value) 920 | 921 | def _write(self, value): 922 | if type(value) is not tuple: 923 | value = (value, ) * 3 924 | for led, v in zip(self._leds, value): 925 | led.value = v 926 | 927 | @property 928 | def value(self): 929 | """ 930 | Represents the colour of the LED as an RGB 3-tuple of ``(red, green, 931 | blue)`` where each value is between 0 and 1 if *pwm* was :data:`True` 932 | when the class was constructed (but only takes values of 0 or 1 otherwise). 933 | For example, red would be ``(1, 0, 0)`` and yellow would be ``(1, 1, 934 | 0)``, whereas orange would be ``(1, 0.5, 0)``. 935 | """ 936 | return tuple(led.value for led in self._leds) 937 | 938 | @value.setter 939 | def value(self, value): 940 | self._stop_change() 941 | self._write(value) 942 | 943 | @property 944 | def is_active(self): 945 | """ 946 | Returns :data:`True` if the LED is currently active (not black) and 947 | :data:`False` otherwise. 948 | """ 949 | return self.value != (0, 0, 0) 950 | 951 | is_lit = is_active 952 | 953 | def _to_255(self, value): 954 | return round(value * 255) 955 | 956 | def _from_255(self, value): 957 | return 0 if value == 0 else value / 255 958 | 959 | @property 960 | def color(self): 961 | """ 962 | Represents the colour of the LED as an RGB 3-tuple of ``(red, green, 963 | blue)`` where each value is between 0 and 255 if *pwm* was :data:`True` 964 | when the class was constructed (but only takes values of 0 or 255 otherwise). 965 | For example, red would be ``(255, 0, 0)`` and yellow would be ``(255, 255, 966 | 0)``, whereas orange would be ``(255, 127, 0)``. 967 | """ 968 | return tuple(self._to_255(v) for v in self.value) 969 | 970 | @color.setter 971 | def color(self, value): 972 | self.value = tuple(self._from_255(v) for v in value) 973 | 974 | @property 975 | def red(self): 976 | """ 977 | Represents the red component of the LED as a value between 0 and 255 if *pwm* was :data:`True` 978 | when the class was constructed (but only takes values of 0 or 255 otherwise). 979 | """ 980 | return self._to_255(self.value[0]) 981 | 982 | @red.setter 983 | def red(self, value): 984 | r, g, b = self.value 985 | self.value = self._from_255(value), g, b 986 | 987 | @property 988 | def green(self): 989 | """ 990 | Represents the green component of the LED as a value between 0 and 255 if *pwm* was :data:`True` 991 | when the class was constructed (but only takes values of 0 or 255 otherwise). 992 | """ 993 | return self._to_255(self.value[1]) 994 | 995 | @green.setter 996 | def green(self, value): 997 | r, g, b = self.value 998 | self.value = r, self._from_255(value), b 999 | 1000 | @property 1001 | def blue(self): 1002 | """ 1003 | Represents the blue component of the LED as a value between 0 and 255 if *pwm* was :data:`True` 1004 | when the class was constructed (but only takes values of 0 or 255 otherwise). 1005 | """ 1006 | return self._to_255(self.value[2]) 1007 | 1008 | @blue.setter 1009 | def blue(self, value): 1010 | r, g, b = self.value 1011 | self.value = r, g, self._from_255(value) 1012 | 1013 | def on(self): 1014 | """ 1015 | Turns the LED on. This is equivalent to setting the LED color to white, e.g. 1016 | ``(1, 1, 1)``. 1017 | """ 1018 | self.value = (1, 1, 1) 1019 | 1020 | def invert(self): 1021 | """ 1022 | Inverts the state of the device. If the device is currently off 1023 | (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on 1024 | (:attr:`value` is ``(1, 1, 1)``). If the device has a specific colour, 1025 | this method inverts the colour. 1026 | """ 1027 | r, g, b = self.value 1028 | self.value = (1 - r, 1 - g, 1 - b) 1029 | 1030 | def toggle(self): 1031 | """ 1032 | Toggles the state of the device. If the device has a specific colour, then that colour is saved and the device is turned off. 1033 | If the device is off, it will be changed to the last colour it had when it was on or, if none, to fully on (:attr:`value` is ``(1, 1, 1)``). 1034 | """ 1035 | if self.value == (0, 0, 0): 1036 | self.value = self._last or (1, 1, 1) 1037 | else: 1038 | self._last = self.value 1039 | self.value = (0, 0, 0) 1040 | 1041 | def blink(self, on_times=1, fade_times=0, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, wait=False, fps=25): 1042 | """ 1043 | Makes the device blink between colours repeatedly. 1044 | 1045 | :param float on_times: 1046 | Single value or tuple of numbers of seconds to stay on each colour. Defaults to 1 second. 1047 | :param float fade_times: 1048 | Single value or tuple of times to fade between each colour. Must be 0 if 1049 | *pwm* was :data:`False` when the class was constructed. 1050 | :type colors: tuple 1051 | Tuple of colours to blink between, use ``(0, 0, 0)`` for off. 1052 | :param colors: 1053 | The colours to blink between. Defaults to red, green, blue. 1054 | :type n: int or None 1055 | :param n: 1056 | Number of times to blink; :data:`None` (the default) means forever. 1057 | :param bool wait: 1058 | If :data:`False` (the default), use a Timer to manage blinking, 1059 | continue blinking, and return immediately. If :data:`False`, only 1060 | return when the blinking is finished (warning: the default value of 1061 | *n* will result in this method never returning). 1062 | """ 1063 | self.off() 1064 | 1065 | if type(on_times) is not tuple: 1066 | on_times = (on_times, ) * len(colors) 1067 | if type(fade_times) is not tuple: 1068 | fade_times = (fade_times, ) * len(colors) 1069 | # If any value is above zero then treat all as 0-255 values 1070 | if any(v > 1 for v in sum(colors, ())): 1071 | colors = tuple(tuple(self._from_255(v) for v in t) for t in colors) 1072 | 1073 | def blink_generator(): 1074 | 1075 | # Define a linear interpolation between 1076 | # off_color and on_color 1077 | 1078 | lerp = lambda t, fade_in, color1, color2: tuple( 1079 | (1 - t) * off + t * on 1080 | if fade_in else 1081 | (1 - t) * on + t * off 1082 | for off, on in zip(color2, color1) 1083 | ) 1084 | 1085 | for c in range(len(colors)): 1086 | if on_times[c] > 0: 1087 | yield (colors[c], on_times[c]) 1088 | 1089 | if fade_times[c] > 0: 1090 | for i in range(int(fps * fade_times[c])): 1091 | v = lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]) 1092 | t = 1 / fps 1093 | yield (v, t) 1094 | 1095 | self._start_change(blink_generator, n, wait) 1096 | 1097 | def pulse(self, fade_times=1, colors=((0, 0, 0), (1, 0, 0), (0, 0, 0), (0, 1, 0), (0, 0, 0), (0, 0, 1)), n=None, wait=False, fps=25): 1098 | """ 1099 | Makes the device fade between colours repeatedly. 1100 | 1101 | :param float fade_times: 1102 | Single value or tuple of numbers of seconds to spend fading. Defaults to 1. 1103 | :param float fade_out_time: 1104 | Number of seconds to spend fading out. Defaults to 1. 1105 | :type colors: tuple 1106 | :param on_color: 1107 | Tuple of colours to pulse between in order. Defaults to red, off, green, off, blue, off. 1108 | :type off_color: ~colorzero.Color or tuple 1109 | :type n: int or None 1110 | :param n: 1111 | Number of times to pulse; :data:`None` (the default) means forever. 1112 | """ 1113 | on_times = 0 1114 | self.blink(on_times, fade_times, colors, n, wait, fps) 1115 | 1116 | def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, wait=False, fps=25): 1117 | """ 1118 | Makes the device fade in and out repeatedly. 1119 | 1120 | :param float fade_times: 1121 | Single value or tuple of numbers of seconds to spend fading between colours. Defaults to 1. 1122 | :param float fade_times: 1123 | Number of seconds to spend fading out. Defaults to 1. 1124 | :type colors: tuple 1125 | :param on_color: 1126 | Tuple of colours to cycle between. Defaults to red, green, blue. 1127 | :type n: int or None 1128 | :param n: 1129 | Number of times to cycle; :data:`None` (the default) means forever. 1130 | """ 1131 | on_times = 0 1132 | self.blink(on_times, fade_times, colors, n, wait, fps) 1133 | 1134 | def close(self): 1135 | super().close() 1136 | for led in self._leds: 1137 | led.close() 1138 | self._leds = None 1139 | 1140 | RGBLED.colour = RGBLED.color 1141 | 1142 | class Motor(PinsMixin): 1143 | """ 1144 | Represents a motor connected to a motor controller that has a two-pin 1145 | input. One pin drives the motor "forward", the other drives the motor 1146 | "backward". 1147 | 1148 | :type forward: int 1149 | :param forward: 1150 | The GP pin that controls the "forward" motion of the motor. 1151 | 1152 | :type backward: int 1153 | :param backward: 1154 | The GP pin that controls the "backward" motion of the motor. 1155 | 1156 | :param bool pwm: 1157 | If :data:`True` (the default), PWM pins are used to drive the motor. 1158 | When using PWM pins, values between 0 and 1 can be used to set the 1159 | speed. 1160 | 1161 | """ 1162 | def __init__(self, forward, backward, pwm=True): 1163 | self._pin_nums = (forward, backward) 1164 | self._forward = PWMOutputDevice(forward) if pwm else DigitalOutputDevice(forward) 1165 | self._backward = PWMOutputDevice(backward) if pwm else DigitalOutputDevice(backward) 1166 | 1167 | def on(self, speed=1, t=None, wait=False): 1168 | """ 1169 | Turns the motor on and makes it turn. 1170 | 1171 | :param float speed: 1172 | The speed as a value between -1 and 1: 1 turns the motor at 1173 | full speed in one direction, -1 turns the motor at full speed in 1174 | the opposite direction. Defaults to 1. 1175 | 1176 | :param float t: 1177 | The time in seconds that the motor should run for. If None is 1178 | specified, the motor will stay on. The default is None. 1179 | 1180 | :param bool wait: 1181 | If True, the method will block until the time `t` has expired. 1182 | If False, the method will return and the motor will turn on in 1183 | the background. Defaults to False. Only effective if `t` is not 1184 | None. 1185 | """ 1186 | if speed > 0: 1187 | self._backward.off() 1188 | self._forward.on(speed, t, wait) 1189 | 1190 | elif speed < 0: 1191 | self._forward.off() 1192 | self._backward.on(-speed, t, wait) 1193 | 1194 | else: 1195 | self.off() 1196 | 1197 | def off(self): 1198 | """ 1199 | Stops the motor turning. 1200 | """ 1201 | self._backward.off() 1202 | self._forward.off() 1203 | 1204 | @property 1205 | def value(self): 1206 | """ 1207 | Sets or returns the motor speed as a value between -1 and 1: -1 is full 1208 | speed "backward", 1 is full speed "forward", 0 is stopped. 1209 | """ 1210 | return self._forward.value + (-self._backward.value) 1211 | 1212 | @value.setter 1213 | def value(self, value): 1214 | if value != 0: 1215 | self.on(value) 1216 | else: 1217 | self.stop() 1218 | 1219 | def forward(self, speed=1, t=None, wait=False): 1220 | """ 1221 | Makes the motor turn "forward". 1222 | 1223 | :param float speed: 1224 | The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. 1225 | 1226 | :param float t: 1227 | The time in seconds that the motor should turn for. If None is 1228 | specified, the motor will stay on. The default is None. 1229 | 1230 | :param bool wait: 1231 | If True, the method will block until the time `t` has expired. 1232 | If False, the method will return and the motor will turn on in 1233 | the background. Defaults to False. Only effective if `t` is not 1234 | None. 1235 | """ 1236 | self.on(speed, t, wait) 1237 | 1238 | def backward(self, speed=1, t=None, wait=False): 1239 | """ 1240 | Makes the motor turn "backward". 1241 | 1242 | :param float speed: 1243 | The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. 1244 | 1245 | :param float t: 1246 | The time in seconds that the motor should turn for. If None is 1247 | specified, the motor will stay on. The default is None. 1248 | 1249 | :param bool wait: 1250 | If True, the method will block until the time `t` has expired. 1251 | If False, the method will return and the motor will turn on in 1252 | the background. Defaults to False. Only effective if `t` is not 1253 | None. 1254 | """ 1255 | self.on(-speed, t, wait) 1256 | 1257 | def close(self): 1258 | """ 1259 | Closes the device and releases any resources. Once closed, the device 1260 | can no longer be used. 1261 | """ 1262 | self._forward.close() 1263 | self._backward.close() 1264 | 1265 | Motor.start = Motor.on 1266 | Motor.stop = Motor.off 1267 | 1268 | class Robot: 1269 | """ 1270 | Represents a generic dual-motor robot / rover / buggy. 1271 | 1272 | Alias for :class:`Rover`. 1273 | 1274 | This class is constructed with two tuples representing the forward and 1275 | backward pins of the left and right controllers. For example, 1276 | if the left motor's controller is connected to pins 12 and 13, while the 1277 | right motor's controller is connected to pins 14 and 15, then the following 1278 | example will drive the robot forward:: 1279 | 1280 | from picozero import Robot 1281 | 1282 | robot = Robot(left=(12, 13), right=(14, 15)) 1283 | robot.forward() 1284 | 1285 | :param tuple left: 1286 | A tuple of two pins representing the forward and backward inputs of the 1287 | left motor's controller. 1288 | 1289 | :param tuple right: 1290 | A tuple of two pins representing the forward and backward inputs of the 1291 | right motor's controller. 1292 | 1293 | :param bool pwm: 1294 | If :data:`True` (the default), pwm pins will be used, allowing variable 1295 | speed control. 1296 | 1297 | """ 1298 | def __init__(self, left, right, pwm=True): 1299 | self._left = Motor(left[0], left[1], pwm) 1300 | self._right = Motor(right[0], right[1], pwm) 1301 | 1302 | @property 1303 | def left_motor(self): 1304 | """ 1305 | Returns the left :class:`Motor`. 1306 | """ 1307 | return self._left 1308 | 1309 | @property 1310 | def right_motor(self): 1311 | """ 1312 | Returns the right :class:`Motor`. 1313 | """ 1314 | return self._right 1315 | 1316 | @property 1317 | def value(self): 1318 | """ 1319 | Represents the motion of the robot as a tuple of (left_motor_speed, 1320 | right_motor_speed) with ``(-1, -1)`` representing full speed backwards, 1321 | ``(1, 1)`` representing full speed forwards, and ``(0, 0)`` 1322 | representing stopped. 1323 | """ 1324 | return (self._left.value, self._right.value) 1325 | 1326 | @value.setter 1327 | def value(self, value): 1328 | self._left.value, self._right.value = value 1329 | 1330 | def forward(self, speed=1, t=None, wait=False): 1331 | """ 1332 | Makes the robot move "forward". 1333 | 1334 | :param float speed: 1335 | The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. 1336 | 1337 | :param float t: 1338 | The time in seconds that the robot should move for. If None is 1339 | specified, the robot will continue to move until stopped. The default 1340 | is None. 1341 | 1342 | :param bool wait: 1343 | If True, the method will block until the time `t` has expired. 1344 | If False, the method will return and the motor will turn on in 1345 | the background. Defaults to False. Only effective if `t` is not 1346 | None. 1347 | """ 1348 | self._left.forward(speed, t, False) 1349 | self._right.forward(speed, t, wait) 1350 | 1351 | def backward(self, speed=1, t=None, wait=False): 1352 | """ 1353 | Makes the robot move "backward". 1354 | 1355 | :param float speed: 1356 | The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. 1357 | 1358 | :param float t: 1359 | The time in seconds that the robot should move for. If None is 1360 | specified, the robot will continue to move until stopped. The default 1361 | is None. 1362 | 1363 | :param bool wait: 1364 | If True, the method will block until the time `t` has expired. 1365 | If False, the method will return and the motor will turn on in 1366 | the background. Defaults to False. Only effective if `t` is not 1367 | None. 1368 | """ 1369 | self._left.backward(speed, t, False) 1370 | self._right.backward(speed, t, wait) 1371 | 1372 | def left(self, speed=1, t=None, wait=False): 1373 | """ 1374 | Makes the robot turn "left" by turning the left motor backward and the 1375 | right motor forward. 1376 | 1377 | :param float speed: 1378 | The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. 1379 | 1380 | :param float t: 1381 | The time in seconds that the robot should turn for. If None is 1382 | specified, the robot will continue to turn until stopped. The default 1383 | is None. 1384 | 1385 | :param bool wait: 1386 | If True, the method will block until the time `t` has expired. 1387 | If False, the method will return and the motor will turn on in 1388 | the background. Defaults to False. Only effective if `t` is not 1389 | None. 1390 | """ 1391 | self._left.backward(speed, t, False) 1392 | self._right.forward(speed, t, wait) 1393 | 1394 | def right(self, speed=1, t=None, wait=False): 1395 | """ 1396 | Makes the robot turn "right" by turning the left motor forward and the 1397 | right motor backward. 1398 | 1399 | :param float speed: 1400 | The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. 1401 | 1402 | :param float t: 1403 | The time in seconds that the robot should turn for. If None is 1404 | specified, the robot will continue to turn until stopped. The default 1405 | is None. 1406 | 1407 | :param bool wait: 1408 | If True, the method will block until the time `t` has expired. 1409 | If False, the method will return and the motor will turn on in 1410 | the background. Defaults to False. Only effective if `t` is not 1411 | None. 1412 | """ 1413 | self._left.forward(speed, t, False) 1414 | self._right.backward(speed, t, wait) 1415 | 1416 | def stop(self): 1417 | """ 1418 | Stops the robot. 1419 | """ 1420 | self._left.stop() 1421 | self._right.stop() 1422 | 1423 | def close(self): 1424 | """ 1425 | Closes the device and releases any resources. Once closed, the device 1426 | can no longer be used. 1427 | """ 1428 | self._left.close() 1429 | self._right.close() 1430 | 1431 | Rover = Robot 1432 | 1433 | class Servo(PWMOutputDevice): 1434 | """ 1435 | Represents a PWM-controlled servo motor. 1436 | 1437 | Setting the `value` to 0 will move the servo to its minimum position, 1438 | 1 will move the servo to its maximum position. Setting the `value` to 1439 | :data:`None` will turn the servo "off" (i.e. no signal is sent). 1440 | 1441 | :type pin: int 1442 | :param pin: 1443 | The pin the servo motor is connected to. 1444 | 1445 | :param bool initial_value: 1446 | If :data:`0`, the servo will be set to its minimum position. If 1447 | :data:`1`, the servo will set to its maximum position. If :data:`None` 1448 | (the default), the position of the servo will not change. 1449 | 1450 | :param float min_pulse_width: 1451 | The pulse width corresponding to the servo's minimum position. This 1452 | defaults to 1ms. 1453 | 1454 | :param float max_pulse_width: 1455 | The pulse width corresponding to the servo's maximum position. This 1456 | defaults to 2ms. 1457 | 1458 | :param float frame_width: 1459 | The length of time between servo control pulses measured in seconds. 1460 | This defaults to 20ms which is a common value for servos. 1461 | 1462 | :param int duty_factor: 1463 | The duty factor of the PWM signal. This is a value between 0 and 65535. 1464 | Defaults to 65535. 1465 | """ 1466 | def __init__(self, pin, initial_value=None, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000, duty_factor=65535): 1467 | self._min_duty = int((min_pulse_width / frame_width) * duty_factor) 1468 | self._max_duty = int((max_pulse_width / frame_width) * duty_factor) 1469 | 1470 | super().__init__(pin, freq=int(1 / frame_width), duty_factor=duty_factor, initial_value=initial_value) 1471 | 1472 | def _state_to_value(self, state): 1473 | return None if state == 0 else clamp((state - self._min_duty) / (self._max_duty - self._min_duty), 0, 1) 1474 | 1475 | def _value_to_state(self, value): 1476 | return 0 if value is None else int(self._min_duty + ((self._max_duty - self._min_duty) * value)) 1477 | 1478 | def min(self): 1479 | """ 1480 | Set the servo to its minimum position. 1481 | """ 1482 | self.value = 0 1483 | 1484 | def mid(self): 1485 | """ 1486 | Set the servo to its mid-point position. 1487 | """ 1488 | self.value = 0.5 1489 | 1490 | def max(self): 1491 | """ 1492 | Set the servo to its maximum position. 1493 | """ 1494 | self.value = 1 1495 | 1496 | def off(self): 1497 | """ 1498 | Turn the servo "off" by setting the value to `None`. 1499 | """ 1500 | self.value = None 1501 | 1502 | ############################################################################### 1503 | # INPUT DEVICES 1504 | ############################################################################### 1505 | 1506 | class InputDevice: 1507 | """ 1508 | Base class for input devices. 1509 | """ 1510 | def __init__(self, active_state=None): 1511 | self._active_state = active_state 1512 | 1513 | @property 1514 | def active_state(self): 1515 | """ 1516 | Sets or returns the active state of the device. If :data:`None` (the default), 1517 | the device will return the value that the pin is set to. If 1518 | :data:`True`, the device will return :data:`True` if the pin is 1519 | HIGH. If :data:`False`, the device will return :data:`False` if the 1520 | pin is LOW. 1521 | """ 1522 | return self._active_state 1523 | 1524 | @active_state.setter 1525 | def active_state(self, value): 1526 | self._active_state = True if value else False 1527 | self._inactive_state = False if value else True 1528 | 1529 | @property 1530 | def value(self): 1531 | """ 1532 | Returns the current value of the device. This is either :data:`True` 1533 | or :data:`False` depending on the value of :attr:`active_state`. 1534 | """ 1535 | return self._read() 1536 | 1537 | class DigitalInputDevice(InputDevice, PinMixin): 1538 | """ 1539 | Represents a generic input device with digital functionality e.g. buttons 1540 | that can be either active or inactive. 1541 | 1542 | :param int pin: 1543 | The pin that the device is connected to. 1544 | 1545 | :param bool pull_up: 1546 | If :data:`True`, the device will be pulled up to HIGH. If 1547 | :data:`False` (the default), the device will be pulled down to LOW. 1548 | 1549 | :param bool active_state: 1550 | If :data:`True` (the default), the device will return :data:`True` 1551 | if the pin is HIGH. If :data:`False`, the device will return 1552 | :data:`False` if the pin is LOW. 1553 | 1554 | :param float bounce_time: 1555 | The bounce time for the device. If set, the device will ignore 1556 | any button presses that happen within the bounce time after a 1557 | button release. This is useful to prevent accidental button 1558 | presses from registering as multiple presses. The default is 1559 | :data:`None`. 1560 | """ 1561 | def __init__(self, pin, pull_up=False, active_state=None, bounce_time=None): 1562 | super().__init__(active_state) 1563 | self._pin_num = pin 1564 | self._pin = Pin( 1565 | pin, 1566 | mode=Pin.IN, 1567 | pull=Pin.PULL_UP if pull_up else Pin.PULL_DOWN) 1568 | self._bounce_time = bounce_time 1569 | 1570 | if active_state is None: 1571 | self._active_state = False if pull_up else True 1572 | else: 1573 | self._active_state = active_state 1574 | 1575 | self._state = self._pin.value() 1576 | 1577 | self._when_activated = None 1578 | self._when_deactivated = None 1579 | 1580 | # setup interupt 1581 | self._pin.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) 1582 | 1583 | def _state_to_value(self, state): 1584 | return int(bool(state) == self._active_state) 1585 | 1586 | def _read(self): 1587 | return self._state_to_value(self._state) 1588 | 1589 | def _pin_change(self, p): 1590 | # turn off the interupt 1591 | p.irq(handler=None) 1592 | 1593 | last_state = p.value() 1594 | 1595 | if self._bounce_time is not None: 1596 | # wait for stability 1597 | stop = ticks_ms() + (self._bounce_time * 1000) 1598 | while ticks_ms() < stop: 1599 | # keep checking, reset the stop if the value changes 1600 | if p.value() != last_state: 1601 | stop = ticks_ms() + self._bounce_time 1602 | last_state = p.value() 1603 | 1604 | # re-enable the interupt 1605 | p.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) 1606 | 1607 | # did the value actually change? 1608 | if self._state != last_state: 1609 | # set the state 1610 | self._state = self._pin.value() 1611 | 1612 | # manage call backs 1613 | callback_to_run = None 1614 | if self.value and self._when_activated is not None: 1615 | callback_to_run = self._when_activated 1616 | 1617 | elif not self.value and self._when_deactivated is not None: 1618 | callback_to_run = self._when_deactivated 1619 | 1620 | if callback_to_run is not None: 1621 | 1622 | def schedule_callback(callback): 1623 | callback() 1624 | 1625 | try: 1626 | schedule(schedule_callback, callback_to_run) 1627 | 1628 | except RuntimeError as e: 1629 | if str(e) == "schedule queue full": 1630 | raise EventFailedScheduleQueueFull( 1631 | "{} - {} not run due to the micropython schedule being full".format( 1632 | str(self), callback_to_run.__name__)) 1633 | else: 1634 | raise e 1635 | 1636 | @property 1637 | def is_active(self): 1638 | """ 1639 | Returns :data:`True` if the device is active. 1640 | """ 1641 | return bool(self.value) 1642 | 1643 | @property 1644 | def is_inactive(self): 1645 | """ 1646 | Returns :data:`True` if the device is inactive. 1647 | """ 1648 | return not bool(self.value) 1649 | 1650 | @property 1651 | def when_activated(self): 1652 | """ 1653 | Returns a :samp:`callback` that will be called when the device is activated. 1654 | """ 1655 | return self._when_activated 1656 | 1657 | @when_activated.setter 1658 | def when_activated(self, value): 1659 | self._when_activated = value 1660 | 1661 | @property 1662 | def when_deactivated(self): 1663 | """ 1664 | Returns a :samp:`callback` that will be called when the device is deactivated. 1665 | """ 1666 | return self._when_deactivated 1667 | 1668 | @when_deactivated.setter 1669 | def when_deactivated(self, value): 1670 | self._when_deactivated = value 1671 | 1672 | def close(self): 1673 | """ 1674 | Closes the device and releases any resources. Once closed, the device 1675 | can no longer be used. 1676 | """ 1677 | self._pin.irq(handler=None) 1678 | self._pin = None 1679 | 1680 | class Switch(DigitalInputDevice): 1681 | """ 1682 | Represents a toggle switch, which is either open or closed. 1683 | 1684 | :param int pin: 1685 | The pin that the device is connected to. 1686 | 1687 | :param bool pull_up: 1688 | If :data:`True` (the default), the device will be pulled up to 1689 | HIGH. If :data:`False`, the device will be pulled down to LOW. 1690 | 1691 | :param float bounce_time: 1692 | The bounce time for the device. If set, the device will ignore 1693 | any button presses that happen within the bounce time after a 1694 | button release. This is useful to prevent accidental button 1695 | presses from registering as multiple presses. Defaults to 0.02 1696 | seconds. 1697 | """ 1698 | def __init__(self, pin, pull_up=True, bounce_time=0.02): 1699 | super().__init__(pin=pin, pull_up=pull_up, bounce_time=bounce_time) 1700 | 1701 | Switch.is_closed = Switch.is_active 1702 | Switch.is_open = Switch.is_inactive 1703 | Switch.when_closed = Switch.when_activated 1704 | Switch.when_opened = Switch.when_deactivated 1705 | 1706 | class Button(Switch): 1707 | """ 1708 | Represents a push button, which can be either pressed or released. 1709 | 1710 | :param int pin: 1711 | The pin that the device is connected to. 1712 | 1713 | :param bool pull_up: 1714 | If :data:`True` (the default), the device will be pulled up to 1715 | HIGH. If :data:`False`, the device will be pulled down to LOW. 1716 | 1717 | :param float bounce_time: 1718 | The bounce time for the device. If set, the device will ignore 1719 | any button presses that happen within the bounce time after a 1720 | button release. This is useful to prevent accidental button 1721 | presses from registering as multiple presses. Defaults to 0.02 1722 | seconds. 1723 | """ 1724 | pass 1725 | 1726 | Button.is_pressed = Button.is_active 1727 | Button.is_released = Button.is_inactive 1728 | Button.when_pressed = Button.when_activated 1729 | Button.when_released = Button.when_deactivated 1730 | 1731 | class AnalogInputDevice(InputDevice, PinMixin): 1732 | """ 1733 | Represents a generic input device with analogue functionality, e.g. 1734 | a potentiometer. 1735 | 1736 | :param int pin: 1737 | The pin that the device is connected to. 1738 | 1739 | :param active_state: 1740 | The active state of the device. If :data:`True` (the default), 1741 | the :class:`AnalogInputDevice` will assume that the device is 1742 | active when the pin is high and above the threshold. If 1743 | ``active_state`` is ``False``, the device will be active when 1744 | the pin is low and below the threshold. 1745 | 1746 | :param float threshold: 1747 | The threshold that the device must be above or below to be 1748 | considered active. The default is 0.5. 1749 | 1750 | """ 1751 | def __init__(self, pin, active_state=True, threshold=0.5): 1752 | self._pin_num = pin 1753 | super().__init__(active_state) 1754 | self._adc = ADC(pin) 1755 | self._threshold = float(threshold) 1756 | 1757 | def _state_to_value(self, state): 1758 | return (state if self.active_state else 65535 - state) / 65535 1759 | 1760 | def _value_to_state(self, value): 1761 | return int(65535 * (value if self.active_state else 1 - value)) 1762 | 1763 | def _read(self): 1764 | return self._state_to_value(self._adc.read_u16()) 1765 | 1766 | @property 1767 | def threshold(self): 1768 | """ 1769 | The threshold that the device must be above or below to be 1770 | considered active. The default is 0.5. 1771 | """ 1772 | return self._threshold 1773 | 1774 | @threshold.setter 1775 | def threshold(self, value): 1776 | self._threshold = float(value) 1777 | 1778 | @property 1779 | def is_active(self): 1780 | """ 1781 | Returns :data:`True` if the device is active. 1782 | """ 1783 | return self.value > self.threshold 1784 | 1785 | @property 1786 | def voltage(self): 1787 | """ 1788 | Returns the voltage of the analogue device. 1789 | """ 1790 | return self.value * 3.3 1791 | 1792 | def close(self): 1793 | self._adc = None 1794 | 1795 | class Potentiometer(AnalogInputDevice): 1796 | """ 1797 | Represents a potentiometer, which outputs a variable voltage 1798 | between 0 and 3.3V. 1799 | 1800 | Alias for :class:`Pot`. 1801 | 1802 | :param int pin: 1803 | The pin that the device is connected to. 1804 | 1805 | :param active_state: 1806 | The active state of the device. If :data:`True` (the default), 1807 | the :class:`AnalogInputDevice` will assume that the device is 1808 | active when the pin is high and above the threshold. If 1809 | ``active_state`` is ``False``, the device will be active when 1810 | the pin is low and below the threshold. 1811 | 1812 | :param float threshold: 1813 | The threshold that the device must be above or below to be 1814 | considered active. The default is 0.5. 1815 | 1816 | """ 1817 | pass 1818 | 1819 | Pot = Potentiometer 1820 | 1821 | def pico_temp_conversion(voltage): 1822 | # Formula for calculating temp from voltage for the onboard temperature sensor 1823 | return 27 - (voltage - 0.706)/0.001721 1824 | 1825 | class TemperatureSensor(AnalogInputDevice): 1826 | """ 1827 | Represents a TemperatureSensor, which outputs a variable voltage. The voltage 1828 | can be converted to a temperature using a `conversion` function passed as a 1829 | parameter. 1830 | 1831 | Alias for :class:`Thermistor` and :class:`TempSensor`. 1832 | 1833 | :param int pin: 1834 | The pin that the device is connected to. 1835 | 1836 | :param active_state: 1837 | The active state of the device. If :data:`True` (the default), 1838 | the :class:`AnalogInputDevice` will assume that the device is 1839 | active when the pin is high and above the threshold. If 1840 | ``active_state`` is ``False``, the device will be active when 1841 | the pin is low and below the threshold. 1842 | 1843 | :param float threshold: 1844 | The threshold that the device must be above or below to be 1845 | considered active. The default is 0.5. 1846 | 1847 | :param float conversion: 1848 | A function that takes a voltage and returns a temperature. 1849 | 1850 | e.g. The internal temperature sensor has a voltage range of 0.706V to 0.716V 1851 | and would use the follow conversion function:: 1852 | 1853 | def temp_conversion(voltage): 1854 | return 27 - (voltage - 0.706)/0.001721 1855 | 1856 | temp_sensor = TemperatureSensor(pin, conversion=temp_conversion) 1857 | 1858 | If :data:`None` (the default), the ``temp`` property will return :data:`None`. 1859 | 1860 | """ 1861 | def __init__(self, pin, active_state=True, threshold=0.5, conversion=None): 1862 | self._conversion = conversion 1863 | super().__init__(pin, active_state, threshold) 1864 | 1865 | @property 1866 | def temp(self): 1867 | """ 1868 | Returns the temperature of the device. If the conversion function is not 1869 | set, this will return :data:`None`. 1870 | """ 1871 | if self._conversion is not None: 1872 | return self._conversion(self.voltage) 1873 | else: 1874 | return None 1875 | 1876 | @property 1877 | def conversion(self): 1878 | """ 1879 | Sets or returns the conversion function for the device. 1880 | """ 1881 | return self._conversion 1882 | 1883 | @conversion.setter 1884 | def conversion(self, value): 1885 | self._conversion = value 1886 | 1887 | pico_temp_sensor = TemperatureSensor(4, True, 0.5, pico_temp_conversion) 1888 | TempSensor = TemperatureSensor 1889 | Thermistor = TemperatureSensor 1890 | 1891 | class DistanceSensor(PinsMixin): 1892 | """ 1893 | Represents a HC-SR04 ultrasonic distance sensor. 1894 | 1895 | :param int echo: 1896 | The pin that the ECHO pin is connected to. 1897 | 1898 | :param int trigger: 1899 | The pin that the TRIG pin is connected to. 1900 | 1901 | :param float max_distance: 1902 | The :attr:`value` attribute reports a normalized value between 0 (too 1903 | close to measure) and 1 (maximum distance). This parameter specifies 1904 | the maximum distance expected in meters. This defaults to 1. 1905 | """ 1906 | def __init__(self, echo, trigger, max_distance=1): 1907 | self._pin_nums = (echo, trigger) 1908 | self._max_distance = max_distance 1909 | self._echo = Pin(echo, mode=Pin.IN, pull=Pin.PULL_DOWN) 1910 | self._trigger = Pin(trigger, mode=Pin.OUT, value=0) 1911 | 1912 | def _read(self): 1913 | echo_on = None 1914 | echo_off = None 1915 | timed_out = False 1916 | 1917 | self._trigger.off() 1918 | sleep(0.000005) 1919 | self._trigger.on() 1920 | sleep(0.00001) 1921 | self._trigger.off() 1922 | 1923 | # If an echo isn't measured in 100 milliseconds, it should 1924 | # be considered out of range. The maximum length of the 1925 | # echo is 38 milliseconds but it's not known how long the 1926 | # transmission takes after the trigger 1927 | stop = ticks_ms() + 100 1928 | while echo_off is None and not timed_out: 1929 | if self._echo.value() == 1 and echo_on is None: 1930 | echo_on = ticks_us() 1931 | if echo_on is not None and self._echo.value() == 0: 1932 | echo_off = ticks_us() 1933 | if ticks_ms() > stop: 1934 | timed_out = True 1935 | 1936 | if echo_off is None or timed_out: 1937 | return None 1938 | else: 1939 | distance = ((echo_off - echo_on) * 0.000343) / 2 1940 | distance = min(distance, self._max_distance) 1941 | return distance 1942 | 1943 | @property 1944 | def value(self): 1945 | """ 1946 | Returns a value between 0, indicating the reflector is either touching 1947 | the sensor or is sufficiently near that the sensor can’t tell the 1948 | difference, and 1, indicating the reflector is at or beyond the 1949 | specified max_distance. A return value of None indicates that the 1950 | echo was not received before the timeout. 1951 | """ 1952 | distance = self.distance 1953 | return distance / self._max_distance if distance is not None else None 1954 | 1955 | @property 1956 | def distance(self): 1957 | """ 1958 | Returns the current distance measured by the sensor in meters. Note 1959 | that this property will have a value between 0 and max_distance. 1960 | """ 1961 | return self._read() 1962 | 1963 | @property 1964 | def max_distance(self): 1965 | """ 1966 | Returns the maximum distance that the sensor will measure in metres. 1967 | """ 1968 | return self._max_distance 1969 | 1970 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | __project__ = 'picozero' 4 | __packages__ = ['picozero'] 5 | __desc__ = 'A beginner-friendly library for using common electronics components with the Raspberry Pi Pico. ' 6 | __version__ = '0.4.1' 7 | __author__ = "Raspberry Pi Foundation" 8 | __author_email__ = 'learning@raspberrypi.org' 9 | __license__ = 'MIT' 10 | __url__ = 'https://github.com/RaspberryPiFoundation/picozero' 11 | __keywords__ = [ 12 | 'raspberry', 13 | 'pi', 14 | 'pico', 15 | 'electronics', 16 | ] 17 | __classifiers__ = [ 18 | 'Development Status :: 4 - Beta', 19 | 'Intended Audience :: Developers', 20 | 'Intended Audience :: Education', 21 | 'Programming Language :: Python :: Implementation :: MicroPython', 22 | ] 23 | __long_description__ = """A beginner-friendly library for using common electronics components with the Raspberry Pi Pico. 24 | 25 | ```python 26 | from picozero import LED, Button 27 | 28 | led = LED(1) 29 | button = Button(2) 30 | 31 | button.when_pressed = led.on 32 | button.when_released = led.off 33 | ``` 34 | 35 | Documentation is available at [picozero.readthedocs.io](https://picozero.readthedocs.io/en/latest/). 36 | """ 37 | 38 | setup( 39 | name=__project__, 40 | version=__version__, 41 | description=__desc__, 42 | long_description=__long_description__, 43 | long_description_content_type='text/markdown', 44 | url=__url__, 45 | author=__author__, 46 | author_email=__author_email__, 47 | license=__license__, 48 | classifiers=__classifiers__, 49 | keywords=__keywords__, 50 | packages=__packages__, 51 | ) -------------------------------------------------------------------------------- /tests/README.rst: -------------------------------------------------------------------------------- 1 | Tests 2 | ===== 3 | 4 | The tests are design to be run on a Raspberry Pi Pico. 5 | 6 | Setup 7 | ----- 8 | 9 | 1. Install the `picozero `_ package. 10 | 11 | 2. Install the `micropython-unittest `_ package. 12 | 13 | 3. Copy the ``test_picozero.py`` to the pico. 14 | 15 | 4. Run the ``test_picozero.py`` file. 16 | 17 | Error messsages 18 | --------------- 19 | 20 | If a test fails it is helpful to be able to see verbose error messages. To see error messages you need to modify the ``lib/unittest.py`` file on the pico. 21 | 22 | Locate the following code in the ``run_class`` function:: 23 | 24 | # Uncomment to investigate failure in detail 25 | #raise 26 | 27 | Uncomment ``raise``:: 28 | 29 | # Uncomment to investigate failure in detail 30 | raise -------------------------------------------------------------------------------- /tests/test_picozero.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from picozero import * 3 | from time import ticks_ms 4 | 5 | def log_device_values(d, timeout): 6 | values = [d.value] 7 | 8 | timeout_ms = ticks_ms() + (timeout * 1000) 9 | 10 | while ticks_ms() < timeout_ms: 11 | if values[-1] != d.value: 12 | values.append(d.value) 13 | 14 | return values 15 | 16 | class MockPin: 17 | def __init__(self, initial_state=0, irq_handler=None): 18 | self._state = initial_state 19 | self._irq_handler = irq_handler 20 | 21 | def read(self): 22 | return self._state 23 | 24 | def write(self, state): 25 | self._state = state 26 | if self._irq_handler is not None: 27 | self._irq_handler(self) 28 | 29 | def value(self, state=None): 30 | if state is None: 31 | return self._state 32 | else: 33 | self.write(state) 34 | 35 | def irq(self, handler, args=None): 36 | self._irq_handler = handler 37 | 38 | class MockADC: 39 | def __init__(self, initial_state=0): 40 | self._state = initial_state 41 | 42 | def read(self): 43 | return self._state 44 | 45 | def write(self, state): 46 | self._state = state 47 | 48 | def read_u16(self): 49 | return self._state 50 | 51 | class MockEvent: 52 | def __init__(self): 53 | self._is_set = False 54 | 55 | def set(self): 56 | self._is_set = True 57 | 58 | def is_set(self): 59 | return self._is_set 60 | 61 | def reset(self): 62 | self._is_set = False 63 | 64 | class Testpicozero(unittest.TestCase): 65 | 66 | def assertInRange(self, value, lower, upper): 67 | msg = "Expected %r to be in range {} to {}".format(lower, upper) 68 | self.assertTrue(value <= upper, msg) 69 | self.assertTrue(value >= lower, msg) 70 | 71 | ########################################################################### 72 | # SUPPORTING 73 | ########################################################################### 74 | 75 | def test_pinout(self): 76 | pins = pinout(output=False) 77 | self.assertIsNotNone(pins) 78 | 79 | ########################################################################### 80 | # OUTPUT DEVICES 81 | ########################################################################### 82 | 83 | def test_digital_output_device_default_values(self): 84 | d = DigitalOutputDevice(1) 85 | 86 | self.assertTrue(d.active_high) 87 | self.assertEqual(d.value, 0) 88 | self.assertFalse(d.is_active) 89 | 90 | d.on() 91 | self.assertTrue(d.value) 92 | self.assertEqual(d._pin.value(), 1) 93 | self.assertTrue(d.is_active) 94 | 95 | d.off() 96 | self.assertFalse(d.value) 97 | self.assertEqual(d._pin.value(), 0) 98 | self.assertFalse(d.is_active) 99 | 100 | d.value = True 101 | self.assertEqual(d.value, 1) 102 | d.value = False 103 | self.assertEqual(d.value, 0) 104 | 105 | d.close() 106 | self.assertIsNone(d._pin) 107 | 108 | def test_digital_output_device_alt_values(self): 109 | d = DigitalOutputDevice(1, active_high=False, initial_value=True) 110 | 111 | self.assertFalse(d.active_high) 112 | self.assertTrue(d.value) 113 | 114 | d.off() 115 | self.assertEqual(d._pin.value(), 1) 116 | 117 | d.on() 118 | self.assertEqual(d._pin.value(), 0) 119 | 120 | d.close() 121 | 122 | def test_digital_output_device_blink(self): 123 | d = DigitalOutputDevice(1) 124 | 125 | d.blink() 126 | values = log_device_values(d, 1.1) 127 | self.assertEqual(values, [1,0]) 128 | d.off() 129 | self.assertFalse(d.value) 130 | 131 | d.blink(on_time=0.1, off_time=0.1, n=2) 132 | values = log_device_values(d, 0.5) 133 | self.assertEqual(values, [1,0,1,0]) 134 | self.assertFalse(d.value) 135 | 136 | d.close() 137 | 138 | def test_digital_LED(self): 139 | d = DigitalLED(1) 140 | self.assertFalse(d.is_lit) 141 | d.close() 142 | 143 | def test_pwm_output_device_default_values(self): 144 | d = PWMOutputDevice(1) 145 | 146 | self.assertTrue(d.active_high) 147 | self.assertEqual(d.value, 0) 148 | self.assertFalse(d.is_active) 149 | self.assertEqual(d.freq, 100) 150 | 151 | d.on() 152 | self.assertTrue(d.value) 153 | self.assertTrue(d.is_active) 154 | self.assertEqual(d._pwm.duty_u16(), 65535) 155 | self.assertTrue(d.is_active) 156 | 157 | d.off() 158 | self.assertFalse(d.value) 159 | self.assertEqual(d._pwm.duty_u16(),0) 160 | self.assertFalse(d.is_active) 161 | 162 | d.value = True 163 | self.assertEqual(d.value, 1) 164 | d.value = False 165 | self.assertEqual(d.value, 0) 166 | 167 | d.value = 0.5 168 | self.assertAlmostEqual(d.value, 0.5, places=2) 169 | self.assertTrue(d.is_active) 170 | 171 | d.close() 172 | self.assertIsNone(d._pwm) 173 | 174 | def test_pwm_output_device_alt_values(self): 175 | d = PWMOutputDevice(1, freq=200, duty_factor=10000, active_high=False, initial_value=True) 176 | 177 | self.assertFalse(d.active_high) 178 | self.assertTrue(d.value) 179 | self.assertEqual(d.freq, 200) 180 | 181 | d.off() 182 | # pwm returns 1 less than the duty_factor unless the duty is set to the maximum 65535 183 | self.assertEqual(d._pwm.duty_u16(), 9999) 184 | self.assertAlmostEqual(d.value, 0, places=2) 185 | 186 | d.on() 187 | self.assertEqual(d._pwm.duty_u16(), 0) 188 | self.assertEqual(d.value, 1) 189 | 190 | d.off() 191 | 192 | d.close() 193 | 194 | def test_pwm_output_device_blink(self): 195 | d = PWMOutputDevice(1) 196 | 197 | d.blink() 198 | values = log_device_values(d, 1.1) 199 | self.assertEqual(values, [1,0]) 200 | d.off() 201 | self.assertFalse(d.value) 202 | 203 | d.blink(on_time=0.1, off_time=0.1, n=2) 204 | values = log_device_values(d, 0.5) 205 | self.assertEqual(values, [1,0,1,0]) 206 | self.assertFalse(d.value) 207 | 208 | d.close() 209 | 210 | def test_pwm_output_device_pulse(self): 211 | d = PWMOutputDevice(1) 212 | 213 | d.pulse(n=1) 214 | values = log_device_values(d, 2.1) 215 | 216 | expected = [ 217 | 0.0, 0.04, 0.08, 0.12, 0.16, 0.2, 0.24, 0.28, 0.32, 0.36, 0.4, 218 | 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.84, 219 | 0.88, 0.92, 0.96, 1.0, 0.96, 0.92, 0.88, 0.84, 0.8, 0.76, 220 | 0.72, 0.68, 0.64, 0.6, 0.56, 0.52, 0.48, 0.44, 0.4, 0.36, 0.32, 221 | 0.28, 0.24, 0.2, 0.16, 0.12, 0.08, 0.04, 0.0] 222 | 223 | if len(values) == len(expected): 224 | for i in range(len(values)): 225 | self.assertAlmostEqual(values[i], expected[i], places=2) 226 | else: 227 | self.fail(f"{len(values)} were generated, {len(expected)} were expected.") 228 | 229 | d.pulse(fade_in_time=0.5, fade_out_time=1, n=1, fps=4) 230 | values = log_device_values(d, 2.1) 231 | 232 | expected = [0.0, 0.5, 1.0, 0.75, 0.5, 0.25, 0] 233 | 234 | if len(values) == len(expected): 235 | for i in range(len(values)): 236 | self.assertAlmostEqual(values[i], expected[i], places=2) 237 | else: 238 | self.fail(f"{len(values)} values were generated, {len(expected)} were expected.") 239 | 240 | d.close() 241 | 242 | def test_motor_default_values(self): 243 | d = Motor(1,2) 244 | 245 | self.assertEqual(d.value, 0) 246 | 247 | d.on() 248 | self.assertEqual(d.value, 1) 249 | 250 | d.stop() 251 | self.assertEqual(d.value, 0) 252 | 253 | d.forward() 254 | self.assertEqual(d.value, 1) 255 | 256 | d.backward() 257 | self.assertEqual(d.value, -1) 258 | 259 | d.value = 0.5 260 | self.assertAlmostEqual(d.value, 0.5, places=2) 261 | 262 | d.value = -0.5 263 | self.assertAlmostEqual(d.value, -0.5, places=2) 264 | 265 | d.forward(1, t=0.5) 266 | values = log_device_values(d, 0.6) 267 | self.assertEqual(values, [1,0]) 268 | self.assertEqual(d.value, 0) 269 | 270 | d.backward(1, t=0.5) 271 | values = log_device_values(d, 0.6) 272 | self.assertEqual(values, [-1,0]) 273 | self.assertEqual(d.value, 0) 274 | 275 | d.close() 276 | 277 | def test_motor_alt_values(self): 278 | d = Motor(1,2,pwm=False) 279 | 280 | d.value = 0.5 281 | self.assertEqual(d.value, 1) 282 | 283 | d.value = -0.5 284 | self.assertEqual(d.value, -1) 285 | 286 | d.value = 0 287 | self.assertEqual(d.value, 0) 288 | 289 | d.close() 290 | 291 | def test_robot(self): 292 | d = Robot(left=(1,2), right=(3,4)) 293 | 294 | d.forward() 295 | self.assertEqual(d.value, (1,1)) 296 | 297 | d.left() 298 | self.assertEqual(d.value, (-1,1)) 299 | 300 | d.right() 301 | self.assertEqual(d.value, (1,-1)) 302 | 303 | d.value = (0.5, -0.5) 304 | self.assertAlmostEqual(d.left_motor.value, 0.5, places=2) 305 | self.assertAlmostEqual(d.right_motor.value, -0.5, places=2) 306 | 307 | d.stop() 308 | self.assertEqual(d.value, (0,0)) 309 | 310 | d.close() 311 | 312 | def test_LED_factory(self): 313 | d = LED(1) 314 | self.assertIsInstance(d, PWMLED) 315 | d.close() 316 | 317 | d = LED(1, pwm=False) 318 | self.assertIsInstance(d, DigitalLED) 319 | d.close() 320 | 321 | def test_pico_led(self): 322 | 323 | self.assertIsInstance(pico_led, DigitalLED) 324 | 325 | self.assertEqual(pico_led.value, 0) 326 | 327 | pico_led.on() 328 | self.assertEqual(pico_led.value, 1) 329 | 330 | pico_led.off() 331 | self.assertEqual(pico_led.value, 0) 332 | 333 | def test_rgb_led_default_values(self): 334 | d = RGBLED(1,2,3) 335 | 336 | self.assertEqual(d.value, (0,0,0)) 337 | 338 | d.on() 339 | self.assertEqual(d.value, (1,1,1)) 340 | 341 | d.off() 342 | self.assertEqual(d.value, (0,0,0)) 343 | 344 | d.value = (0.25, 0.5, 0.75) 345 | self.assertAlmostEqual(d.value[0], 0.25, places=2) 346 | self.assertAlmostEqual(d.value[1], 0.5, places=2) 347 | self.assertAlmostEqual(d.value[2], 0.75, places=2) 348 | 349 | d.red = 200 350 | self.assertAlmostEqual(d.value[0], 0.78, places=2) 351 | 352 | d.green = 100 353 | self.assertAlmostEqual(d.value[1], 0.39, places=2) 354 | 355 | d.blue = 50 356 | self.assertAlmostEqual(d.value[2], 0.20, places=2) 357 | 358 | d.close() 359 | 360 | def test_rgb_led_alt_values(self): 361 | d = RGBLED(1,2,3, initial_value=(1,1,1), pwm=False) 362 | 363 | self.assertEqual(d.value, (1,1,1)) 364 | 365 | d.on() 366 | self.assertEqual(d.value, (1,1,1)) 367 | 368 | d.off() 369 | self.assertEqual(d.value, (0,0,0)) 370 | 371 | d.value = (1, 1, 1) 372 | self.assertEqual(d.value, (1,1,1)) 373 | 374 | d.value = (0, 0.5, 1) 375 | self.assertEqual(d.value, (0,1,1)) 376 | 377 | d.close() 378 | 379 | def test_servo_default_value(self): 380 | d = Servo(1) 381 | 382 | self.assertEqual(d.value, None) 383 | 384 | d.value = 0 385 | self.assertAlmostEqual(d.value, 0, 2) 386 | self.assertInRange(d._pwm.duty_u16(), int((0.001 / 0.02) * 65535) - 1, int((0.001 / 0.02) * 65535) + 1) 387 | 388 | d.value = 1 389 | self.assertAlmostEqual(d.value, 1, 2) 390 | self.assertInRange(d._pwm.duty_u16(), int((0.002 / 0.02) * 65535) - 1, int((0.002 / 0.02) * 65535) + 1) 391 | 392 | d.value = None 393 | self.assertEqual(d.value, None) 394 | self.assertEqual(d._pwm.duty_u16(), 0) 395 | 396 | d.min() 397 | self.assertAlmostEqual(d.value, 0, 2) 398 | 399 | d.mid() 400 | self.assertAlmostEqual(d.value, 0.5, 2) 401 | 402 | d.max() 403 | self.assertAlmostEqual(d.value, 1, 2) 404 | 405 | d.off() 406 | self.assertEqual(d._pwm.duty_u16(), 0) 407 | 408 | d.close() 409 | 410 | def test_servo_alt_values(self): 411 | d = Servo(1, initial_value=1, min_pulse_width=0.9/1000, max_pulse_width=2.1/1000, frame_width=19/1000) 412 | 413 | self.assertAlmostEqual(d.value, 1, 2) 414 | 415 | d.value = 0 416 | self.assertInRange(d._pwm.duty_u16(), int((0.0009 / 0.019) * 65535) - 1, int((0.0009 / 0.019) * 65535) + 1) 417 | 418 | d.value = 1 419 | self.assertInRange(d._pwm.duty_u16(), int((0.0021 / 0.019) * 65535) - 1, int((0.0021 / 0.019) * 65535) + 1) 420 | 421 | d.value = None 422 | self.assertEqual(d._pwm.duty_u16(), 0) 423 | 424 | d.close() 425 | 426 | ########################################################################### 427 | # INPUT DEVICES 428 | ########################################################################### 429 | 430 | def test_digital_input_device_default_values(self): 431 | d = DigitalInputDevice(1) 432 | 433 | pin = MockPin(irq_handler=d._pin_change) 434 | d._pin = pin 435 | 436 | self.assertTrue(d.active_state) 437 | self.assertFalse(d.is_active) 438 | self.assertEqual(d.value, 0) 439 | 440 | pin.write(1) 441 | 442 | self.assertTrue(d.is_active) 443 | self.assertEqual(d.value, 1) 444 | 445 | pin.write(0) 446 | 447 | self.assertFalse(d.is_active) 448 | self.assertEqual(d.value, 0) 449 | 450 | d.close() 451 | 452 | def test_digital_input_device_alt_values(self): 453 | d = DigitalInputDevice(1, pull_up=False, active_state=False) 454 | 455 | pin = MockPin(irq_handler=d._pin_change) 456 | d._pin = pin 457 | 458 | self.assertFalse(d.active_state) 459 | self.assertTrue(d.is_active) 460 | self.assertEqual(d.value, 1) 461 | 462 | pin.write(1) 463 | 464 | self.assertFalse(d.is_active) 465 | self.assertEqual(d.value, 0) 466 | 467 | pin.write(0) 468 | 469 | self.assertTrue(d.is_active) 470 | self.assertEqual(d.value, 1) 471 | 472 | d.close() 473 | 474 | def test_digital_input_device_activated_deactivated(self): 475 | d = DigitalInputDevice(1) 476 | 477 | pin = MockPin(irq_handler=d._pin_change) 478 | d._pin = pin 479 | 480 | event_activated = MockEvent() 481 | event_deactivated = MockEvent() 482 | 483 | d.when_activated = event_activated.set 484 | d.when_deactivated = event_deactivated.set 485 | 486 | self.assertFalse(event_activated.is_set()) 487 | pin.write(1) 488 | self.assertTrue(event_activated.is_set()) 489 | 490 | self.assertFalse(event_deactivated.is_set()) 491 | pin.write(0) 492 | self.assertTrue(event_deactivated.is_set()) 493 | 494 | d.close() 495 | 496 | def test_adc_input_device_default_values(self): 497 | d = AnalogInputDevice(1) 498 | 499 | adc = MockADC() 500 | d._adc = adc 501 | 502 | self.assertTrue(d.active_state) 503 | self.assertFalse(d.is_active) 504 | self.assertEqual(d.value, 0) 505 | 506 | adc.write(65535) 507 | self.assertTrue(d.is_active) 508 | self.assertEqual(d.value, 1) 509 | self.assertEqual(d.voltage, 3.3) 510 | 511 | adc.write(0) 512 | self.assertFalse(d.is_active) 513 | self.assertEqual(d.value, 0) 514 | self.assertEqual(d.voltage, 0) 515 | 516 | # mid point 517 | adc.write(32767) 518 | self.assertAlmostEqual(d.value, 0.5, places=2) 519 | self.assertAlmostEqual(d.voltage, 1.65, places=2) 520 | 521 | d.close() 522 | 523 | def test_adc_input_device_alt_values(self): 524 | d = AnalogInputDevice(1, active_state=False, threshold=0.1) 525 | 526 | adc = MockADC() 527 | d._adc = adc 528 | 529 | self.assertFalse(d.active_state) 530 | self.assertTrue(d.is_active) 531 | self.assertEqual(d.value, 1) 532 | 533 | adc.write(65535) 534 | self.assertFalse(d.is_active) 535 | self.assertEqual(d.value, 0) 536 | self.assertEqual(d.voltage, 0) 537 | 538 | adc.write(0) 539 | self.assertTrue(d.is_active) 540 | self.assertEqual(d.value, 1) 541 | self.assertEqual(d.voltage, 3.3) 542 | 543 | d.close() 544 | 545 | def test_adc_input_device_threshold(self): 546 | d = AnalogInputDevice(1) 547 | 548 | adc = MockADC() 549 | d._adc = adc 550 | 551 | self.assertFalse(d.is_active) 552 | 553 | # mid point 554 | adc.write(32767) 555 | self.assertFalse(d.is_active) 556 | 557 | # above threshold 558 | adc.write(32768) 559 | self.assertTrue(d.is_active) 560 | 561 | # below threshold 562 | adc.write(32766) 563 | self.assertFalse(d.is_active) 564 | 565 | d.threshold = 0.1 566 | 567 | self.assertTrue(d.is_active) 568 | 569 | adc.write(6553) 570 | self.assertFalse(d.is_active) 571 | 572 | d.close() 573 | 574 | def test_temp_sensory(self): 575 | 576 | def temp_conversion(voltage): 577 | return voltage + 2 578 | 579 | t = TemperatureSensor(4, conversion=temp_conversion) 580 | 581 | adc = MockADC() 582 | t._adc = adc 583 | 584 | adc.write(65535) 585 | self.assertEqual(t.temp, 3.3 + 2) 586 | 587 | adc.write(0) 588 | self.assertEqual(t.temp, 2) 589 | 590 | t.close() 591 | 592 | def test_pico_temp_sensor(self): 593 | 594 | self.assertIsInstance(pico_temp_sensor, TemperatureSensor) 595 | self.assertEqual(pico_temp_sensor.pin, 4) 596 | self.assertIsNotNone(pico_temp_sensor.temp) 597 | 598 | unittest.main() 599 | 600 | --------------------------------------------------------------------------------