Compare commits

...
Author SHA1 Message Date
Laurens Valk 252adb3e85 jedi: v1.10.0 2023-10-26 20:43:28 +02:00
Laurens Valk 36ed5917e9 @pybricks/ide-docs v2.10.0 2023-10-26 20:29:33 +02:00
Laurens Valk bec6aaa76e v3.3.0b9 2023-10-26 20:29:31 +02:00
Laurens Valk e7377dff31 pybricks.pupdevices.Motor: Clarify profile setting. 2023-10-26 14:31:16 +02:00
Laurens Valk d935841399 pybricks.robotics.DriveBase: Add brake. 2023-10-26 14:18:56 +02:00
David Lechner 3335042218 ubuiltins: add set class
Add type stubs and docs for builtin `set` type.

Issue: https://github.com/pybricks/support/issues/402
2023-10-26 09:21:41 +02:00
Laurens Valk 209fba5c7e pybricks.tools: Document multitasking. 2023-10-24 17:05:10 +02:00
Laurens Valk faf11581c0 pybricks.tools: Document hub_menu.
Fixes https://github.com/pybricks/pybricks-api/issues/144
2023-10-24 16:23:59 +02:00
Laurens Valk 8819446513 pybricks.common.BLE: Update broadcast API.
Match firmware updates.
2023-10-24 10:40:52 +02:00
Laurens Valk b97eef8152 conf: Fix RTD theme.
Fixes https://github.com/pybricks/pybricks-api/issues/150
2023-10-23 20:49:35 +02:00
Laurens Valk 6bbdccb25f pybricks.hubs.ThisHub.storage: Fix duplicate self.
Fixes https://github.com/pybricks/pybricks-api/issues/146
2023-10-23 16:36:13 +02:00
Laurens Valk 578abab24e signaltypes: Bare minimal async docs.
This is nowhere near complete, but provides handles to start documenting async for the relevant methods.
2023-10-23 16:28:50 +02:00
Laurens Valk 1a49d396d2 pybricks.robotics: Document use_gyro. 2023-10-23 15:39:52 +02:00
Laurens Valk 397663d631 poetry: Update deps and fix Sphinx breakage. 2023-10-23 15:30:19 +02:00
David Lechner 821a3e4455 pybricks.*: add MaybeAwaitable types
Many blocking functions now return Awaitable[T] when the async run
loop is running. The static analysis tools don't have a way of
knowing this, so the best we can do is return both types so that
x = method() and x = await method() both work mostly as expected.
2023-06-12 17:04:09 -05:00
David Lechner 41b9ce71dd pybricks.tools: add read_input_byte()
This adds stubs and docs for the new `pybricks.tools.read_input_byte()`
function.
2023-06-12 11:06:20 -05:00
David Lechner 6d69f04fe5 docs/cityhub: add ble.broadcast
A workaround has been implemented in the firmware so this is available
now.
2023-05-23 13:37:05 -05:00
dependabot[bot] 00ded2e2a0 build(deps): bump requests from 2.28.1 to 2.31.0
Bumps [requests](https://github.com/psf/requests) from 2.28.1 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.28.1...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-23 09:36:46 -05:00
David Lechner dd1d167ad9 pyproject: drop alpha status
We accidentally published the last few versions as alpha. It should
have been beta to match the firmware.
2023-05-19 14:59:25 -05:00
David Lechner 4dc289ad61 pybricks.common.BLE.broadcast: fix missing `
This caused bad formatting.
2023-05-16 16:02:13 -05:00
David Lechner bc592b4469 npm/jedi: v1.9.0 2023-05-16 15:45:40 -05:00
David Lechner 784c7ec5fc jedi: v1.9.0
Update pybricks package for next beta.
2023-05-16 15:43:09 -05:00
David Lechner 9e2bac457a github/workflows/publish-ide-docs: poetry install --only=doc
We don't need to install other dependencies to build the docs.
2023-05-16 15:39:14 -05:00
David Lechner 61d18ebe6f @pybricks/ide-docs v2.9.0 2023-05-16 15:33:45 -05:00
David Lechner ff0b767e3e v3.3.0a5 2023-05-16 15:27:22 -05:00
David Lechner 3274cef849 pybricks.common: Add new BLE class.
This adds a new BLE class that is used for connectionless broadcasting/
observing on hubs with built-in Bluetooth Low Energy.

Also see https://github.com/pybricks/pybricks-micropython/pull/158.
2023-05-16 14:46:53 -05:00
David Lechner b4ff3af36d pyproject: split lint and doc dependencies
This allows a subset of dependencies to be installed if needed and
allows sharing requirements with readthedocs.
2023-05-10 14:35:08 -05:00
David Lechner 8e5891d9e8 all: spelling
Misc spelling fixes.
2023-05-10 14:16:15 -05:00
David Lechner 7de67852f7 vscode: change to ms-python.black-formatter
Microsoft is breaking out various Python tools into separate extensions.
2023-05-10 13:43:49 -05:00
David Lechner 31d3a117d6 doc/main/conf: update copyright year 2023-04-21 17:39:28 -05:00
David Lechner 263fe3a1cb npm/jedi: v1.8.0 2023-04-21 17:31:17 -05:00
David Lechner db8de447d5 jedi: v1.8.0 2023-04-21 17:26:38 -05:00
David Lechner 46d6189eb1 jedi: update pybricks package version
also bump python requirement since Pyodide is now using Python 3.11.
2023-04-21 17:24:47 -05:00
David Lechner 2278c7bcda @pybricks/ide-docs v2.8.0 2023-04-21 17:10:51 -05:00
David Lechner 4da3618428 v3.3.0b4 2023-04-21 17:00:18 -05:00
Laurens Valk 5bc11b185d pybricks.tools: Add versionchanged note. 2023-04-21 14:07:46 +02:00
Laurens Valk be9f99d41b pybricks.tools: Add category headings. 2023-04-21 13:59:16 +02:00
Laurens Valk 870aae83c0 pybricks.tools: Document cross product. 2023-04-21 13:39:08 +02:00
Laurens Valk 9cde749e8a pybricks.geometry: Drop module.
See https://github.com/pybricks/pybricks-micropython/pull/160
2023-04-21 13:33:19 +02:00
Laurens Valk 8bca5eb6e4 pybricks.robotics: Add GyroDriveBase. 2023-04-21 10:42:30 +02:00
Laurens Valk 95a12a7386 pybricks.robotics: Drop note suggesting to flip motors.
This will still work, but it is asking for trouble when including the gyro.

So it is probably better not to mention this at all.
2023-04-21 10:42:30 +02:00
Laurens Valk d6c78f1f39 pybricks.robotics: Drop note restricting when to change settings. 2023-04-21 10:42:30 +02:00
Laurens Valk 98f2bb27fe pybricks.robotics.DriveBase: Drop positive direction.
This reverts commit e4650cb1c9.

This is not needed for gyro support, so it is better to remove it
before it is ever released.
2023-04-21 10:42:30 +02:00
Laurens Valk fdbd078388 pybricks._common.IMU: Heading is clockwise positive.
This is not a breaking change because heading() was never implemented until now.
2023-04-21 10:42:30 +02:00
Laurens Valk ccd2b46819 pybricks.robotics.DriveBase: Revert gyro use via init.
This reverts commit dadaab6f61.

We will introduce a separate class instead.
2023-04-21 10:42:30 +02:00
Laurens Valk c0cb05dd74 pybricks.robotics.DriveBase: Document use of gyro. 2023-04-21 10:42:30 +02:00
Laurens Valk b183e1420a pybricks.pupdevices.Motor: Document speed time window. 2023-04-21 10:42:30 +02:00
Laurens Valk b311c54261 pybricks._common.IMU: Change settings setter. 2023-04-21 10:42:30 +02:00
Laurens Valk bb37b94f5e pybricks._common.IMU: Add imu status. 2023-04-21 10:42:30 +02:00
Laurens Valk 5b25606afa pybricks._common.IMU: Add rotation and orientation.
Also update implementation status for heading and reset_heading.
2023-04-21 10:42:30 +02:00
Laurens Valk 8172d11ef6 urandom: Fix formula in getrandbits. 2023-03-28 08:50:00 +02:00
Laurens Valk c415296b71 pybricks._common.Motor: Document profile.
Fixes https://github.com/pybricks/support/issues/966
2023-03-20 15:12:36 +01:00
Laurens Valk e4650cb1c9 pybricks.robotics.DriveBase: Document positive direction.
Fixes https://github.com/pybricks/support/issues/992
2023-03-20 14:22:25 +01:00
Laurens Valk 91154a394c pybricks.robotics.DriveBase: Clarify settings method.
Fixes https://github.com/pybricks/support/issues/882
2023-03-20 14:00:42 +01:00
David Lechner 3c36010c04 pybricks.ev3devices: Fix Gyro direction parameter name.
The actual implemented name is `direction`, not `positive_direction`.

Fixes: https://github.com/pybricks/support/issues/509
2023-03-20 13:56:16 +01:00
Laurens Valk a553df3b03 pybricks.iodevices.PUPDevice: Add device class as comments. 2023-02-23 16:31:38 +01:00
Hans Willemen dbd1cc8dc9 pybricks.iodevices.PUPDevice: Add more devices.
Added:
- non-uart DCMotors  (Medium and Train)
- non-uart Light
- SPIKE 3x3 Color Matrix
- SPIKE Small Angular Motor
2023-02-23 16:31:38 +01:00
Laurens Valk b40c3989aa pybricks.common.Motor: Add Model() class instance.
This is mainly used to debug the motor model, and to quickly select the
feedback parameters. This is useful when adding or configuring new motor
types.

Reading the estimated speed can also be useful in advanced applications
where the user builds their own PID controller.

We may choose to hide this method from the documentation before the
release.
2023-02-09 10:25:15 +01:00
Laurens Valk 13612bdb1a README: Fix D002 Trailing whitespace. 2023-02-09 10:25:10 +01:00
kai-morich 031e905e12 README: document stub usage in vs code
Fixes: pybricks/support#937
2023-02-07 15:37:15 -06:00
Laurens Valk 428bca79ab pybricks.common.Control: Document deadzone. 2023-02-02 14:45:34 +01:00
Laurens Valk 0e8b6cfd97 doc/main/cad/devices: Generate motor model. 2023-02-02 14:44:50 +01:00
David Lechner eea8ff0924 examples/ev3/bluetooth: remove 3rd party dependency
Since Python 3.10, RFCOMM sockets are available in Python on Windows
so we no longer need 3rd party code.

Fixes: pybricks/support#902
2023-01-06 14:17:29 -06:00
David Lechner 8c673ab280 github: update actions/checkout to v3
This fixes deprecation warnings about node v12.
2022-12-28 16:16:01 -06:00
David Lechner 73477169ae npm/jedi: v1.7.0
Update pybricks_jedi package to v1.7.0 and bump version for release.
2022-12-28 16:13:28 -06:00
David Lechner 5ccc60c177 jedi/pyproject: v1.7.0 2022-12-28 16:09:37 -06:00
David Lechner 93c4fcc884 jedi: add update_user_modules() function
This function will be used to fix import completion of user module
names in Pybricks Code.

Issue: https://github.com/pybricks/support/issues/759
2022-12-28 16:06:47 -06:00
David Lechner aa72605e99 jedi: fix code completion for local _*
This fixes code completion for identifier names starting with "_" in
the local ("__main__") file.
2022-12-28 15:33:33 -06:00
David Lechner 8ee725722f jedi: fix completion of builtin types
Completions for `x.` where `x` is an instance of a builtin type was
broken because of filtering on the `builtins` modules. This extends
the filtering to allow attributes available on common builtin types
in Pybricks MicroPython.
2022-12-28 15:33:33 -06:00
David Lechner 236bda5f31 ubuiltins.round: Fix example.
The example did not have proper line breaks between statements.

Also add an additional example using f-strings.
2022-12-28 14:09:18 -06:00
David Lechner 36ee3bd9ef pyproject: v3.2.0 2022-12-20 15:46:44 -06:00
75 changed files with 3963 additions and 1123 deletions
+3 -11
View File
@@ -6,7 +6,7 @@ name: Build Python package and docs
on:
push:
tags-ignore:
- "*"
- '**'
pull_request:
paths:
- doc/**
@@ -20,19 +20,11 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.8, 3.9]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install poetry
+5 -5
View File
@@ -7,17 +7,17 @@ on:
jobs:
publish_ide_docs:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Ubuntu packages
run: |
sudo apt-get update
sudo apt-get install -y dvisvgm preview-latex-style texlive texlive-fonts-extra texlive-latex-extra
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Python 3.8
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install dependencies
@@ -25,8 +25,8 @@ jobs:
pip install poetry
poetry run python -m pip install --upgrade pip
poetry run python -m pip install --upgrade setuptools
poetry install
- uses: actions/setup-node@v1
poetry install --only=doc
- uses: actions/setup-node@v3
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
publish_jedi:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v3
with:
-22
View File
@@ -1,22 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
#
# Settings in the readthedocs online dashboard will be ignored.
# Required
version: 2
# Build documentation in the doc/main directory with Sphinx
sphinx:
configuration: doc/main/conf.py
# Build your docs in additional formats such as PDF
formats:
- pdf
# Set the version of Python and requirements required to build the docs
python:
version: 3.8
install:
- requirements: rtd-requirements.txt
+25
View File
@@ -0,0 +1,25 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
build:
os: "ubuntu-22.04"
tools:
python: "3.10"
jobs:
post_create_environment:
- pip install poetry
- poetry config virtualenvs.create false
post_install:
- poetry install --only=doc
# Build documentation in the doc/main/ directory with Sphinx
sphinx:
configuration: doc/main/conf.py
# Optionally build your docs in additional formats such as PDF
formats:
- pdf
+2 -1
View File
@@ -4,7 +4,8 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-python.python"
"ms-python.python",
"ms-python.black-formatter"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
+2 -2
View File
@@ -4,14 +4,14 @@
},
"python.defaultInterpreterPath": ".venv/bin/python",
"python.autoComplete.extraPaths": ["jedi/src"],
"python.formatting.provider": "black",
"python.formatting.provider": "none",
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.pycodestyleEnabled": false,
"python.linting.enabled": true,
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.python"
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.languageServer": "Pylance",
"python.testing.pytestArgs": [
+37 -1
View File
@@ -4,7 +4,43 @@
## Unreleased
## 3.2.0c2 - 2022-12-20
## 3.3.0b9 - 2023-10-26
### Changed
- Changed the beta feature for using the hub's gyro. Gyro control can now be
toggled using `use_gyro` instead of using a separate `GyroDriveBase` class.
- Documentation updates to match firmware 3.3.0b5--3.3.0b9 updates.
## Added
- Added `set` to `ubuiltins` module.
- Basic multitasking docs.
- Awaitable keyword for awaitable methods and functions.
## 3.3.0b5 - 2023-05-16
### Added
- Documented new `hub.ble` methods.
## 3.3.0b4 - 2023-04-21
### Added
- Documented `integral_deadzone` in `Control.pid()`.
- Documented `Motor.model`. This can be used to view the estimated motor
state and change its settings.
- Added `rotation`, `orientation`, `ready`, `stationary` and `settings` methods
to `IMU` class.
- Added `GyroDriveBase` class to `pybricks.robotics`.
### Changed
- Change implementation status of `IMU.heading` and `IMU.reset_heading`. They
are now implemented, with some limitations as noted in a note box.
- Moved `Matrix` and `vector` from `pybricks.geometry` to `pybricks.tools`.
- Moved `Axis` from `pybricks.geometry` to `pybricks.parameters`.
### Removed
- Removed `pybricks.geometry` module.
## 3.2.0 - 2022-12-20
### Changed
- Changed module TOC headings to make it easier to find things.
+5
View File
@@ -12,6 +12,11 @@ used to generate the `official documentation`_.
See the `contributor's guide <CONTRIBUTING.md>`_ for acceptable changes and
instructions to build the documentation locally.
You can use the API stubs in this repository for syntax highlighting and code
completion when programming the EV3 with VS Code. To enable, remove the
``"python.languageServer"="None"`` line in the ``.vscode/settings.json`` file
generated by the *LEGO® MINDSTORMS® EV3 MicroPython* extension.
For general discussion, please visit the `support`_ issue tracker.
.. _Pybricks package: pybricks
+14 -13
View File
@@ -145,13 +145,10 @@ add_module_names = False # Hide module name
# -- Options for HTML output ----------------------------------------------
if ON_RTD:
html_theme = "default"
else:
import sphinx_rtd_theme
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_context = {
"disclaimer": _DISCLAIMER,
@@ -321,10 +318,11 @@ def on_missing_reference(
app.builder, node["refdoc"], "signaltypes", "numbers", contnode
)
# References with special characters can't exist, so we have to supress
# References with special characters can't exist, so we have to suppress
# warnings when Sphinx tries to cross reference units like deg/s. For
# consistency, we also treat units without special characters this way.
for unit in [
"dBm",
"deg",
"deg/s",
"deg/s²",
@@ -342,13 +340,16 @@ def on_missing_reference(
"Ω",
"N",
]:
try:
# If they match on raw source, we are dealing with argument types.
if unit == contnode.rawsource:
# Return as-is to suppress missing cross reference warning. We
# could make this more fancy by returning an xref node that links
# to the signals page.
return contnode
# If they match on raw source, we are dealing with argument types.
if unit == contnode.rawsource:
# Return as-is to suppress missing cross reference warning. We
# could make this more fancy by returning an xref node that links
# to the signals page.
return contnode
except AttributeError:
pass
# Return types are denoted as "int: deg"
try:
+4 -5
View File
@@ -10,7 +10,6 @@ FEATURES_SMALL = set()
# Medium feature set.
FEATURES_MEDIUM = FEATURES_SMALL | {
"pybricks-geometry",
"pybricks-common-control",
"pybricks-iodevices",
"stm32-extra",
@@ -24,10 +23,10 @@ FEATURES_LARGE = FEATURES_MEDIUM | set()
HUB_FEATURES = {
"movehub": {"movehub"} | FEATURES_SMALL,
"cityhub": {"cityhub"} | FEATURES_MEDIUM,
"technichub": {"technichub"} | FEATURES_MEDIUM,
"primehub": {"primehub", "inventorhub", "light-matrix"} | FEATURES_LARGE,
"inventorhub": {"primehub", "inventorhub", "light-matrix"} | FEATURES_LARGE,
"essentialhub": {"essentialhub"} | FEATURES_LARGE,
"technichub": {"technichub", "gyro"} | FEATURES_MEDIUM,
"primehub": {"primehub", "inventorhub", "light-matrix", "gyro"} | FEATURES_LARGE,
"inventorhub": {"primehub", "inventorhub", "light-matrix", "gyro"} | FEATURES_LARGE,
"essentialhub": {"essentialhub", "gyro"} | FEATURES_LARGE,
}
+1169 -6
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -8,7 +8,7 @@ import sys
# General information about the project.
project = "pybricks"
copyright = "2018-2021 The Pybricks Authors"
copyright = "2018-2023 The Pybricks Authors"
author = ""
_TITLE = "Pybricks Modules and Examples"
@@ -29,7 +29,7 @@ if os.environ.get("READTHEDOCS", None) == "True":
# HACK: this allows Number type alias to be imported by Sphinx
os.environ["SPHINX_BUILD"] = "True"
# Addtional configuration of the IDE docs
# Additional configuration of the IDE docs
if "ide" in tags.tags: # noqa F821
_DISCLAIMER = ""
html_show_copyright = False
@@ -45,7 +45,7 @@ if "ide" in tags.tags: # noqa F821
exec(open(os.path.abspath("../common/conf.py")).read())
# Addtional configuration of the IDE docs
# Additional configuration of the IDE docs
if "ide" in tags.tags: # noqa F821
extensions.remove("sphinx.ext.mathjax") # noqa F821
+2 -2
View File
@@ -20,12 +20,12 @@ Motors
.. rubric:: Measuring
.. automethod:: pybricks.ev3devices.Motor.speed
.. automethod:: pybricks.ev3devices.Motor.angle
.. automethod:: pybricks.ev3devices.Motor.reset_angle
.. automethod:: pybricks.ev3devices.Motor.speed
.. automethod:: pybricks.ev3devices.Motor.load
.. automethod:: pybricks.ev3devices.Motor.stalled
-51
View File
@@ -1,51 +0,0 @@
.. pybricks-requirements:: stm32-float
:mod:`geometry <pybricks.geometry>` -- Geometry and algebra
============================================================
.. module:: pybricks.geometry
.. autoclass:: pybricks.geometry.Matrix
:no-members:
.. autoattribute:: pybricks.geometry::Matrix.T
.. autoattribute:: pybricks.geometry::Matrix.shape
.. autofunction:: pybricks.geometry.vector
.. autoclass:: pybricks.geometry.Axis
:no-members:
.. _robotframe:
Reference frames
-----------------------
The Pybricks module and this documentation use the following conventions:
- X: Positive means forward. Negative means backward.
- Y: Positive means to the left. Negative means to the right.
- Z: Positive means upward. Negative means downward.
To make sure that all hub measurements (such as acceleration) have the correct
value and sign, you can specify how the hub is mounted in your creation. This
adjust the measurements so that it is easy to see how your *robot* is moving,
rather than how the *hub* is moving.
For example, the hub may be mounted upside down in your design. If you
configure the settings as shown in :numref:`fig_imuexamples`, the hub
measurements will be adjusted accordingly. This way, a positive acceleration
value in the X direction means that your *robot* accelerates forward, even
though the *hub* accelerates backward.
.. _fig_imuexamples:
.. figure:: ../main/diagrams/imuexamples.png
:width: 100 %
How to configure the ``top_side`` and ``front_side`` settings for three
different robot designs. The same technique can be applied to other hubs
and other creations, by noting which way the top and
front :class:`Side <Side>` of the hub are pointing. The example
on the left is the default configuration.
+27
View File
@@ -19,6 +19,16 @@ City Hub
.. automethod:: pybricks.hubs::CityHub.light.animate
.. rubric:: Using connectionless Bluetooth messaging
.. automethod:: pybricks.hubs::CityHub.ble.broadcast
.. automethod:: pybricks.hubs::CityHub.ble.observe
.. automethod:: pybricks.hubs::CityHub.ble.signal_strength
.. automethod:: pybricks.hubs::CityHub.ble.version
.. rubric:: Using the battery
.. automethod:: pybricks.hubs::CityHub.battery.voltage
@@ -70,6 +80,23 @@ Creating light animations
.. literalinclude::
../../../examples/pup/hub_common/build/light_animate_cityhub.py
Bluetooth examples
------------------
Broadcasting data to other hubs
*******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_broadcast_cityhub.py
Observing data from other hubs
******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_observe_cityhub.py
Button and system examples
----------------------------------
+37
View File
@@ -25,6 +25,10 @@ Essential Hub
.. rubric:: Using the IMU
.. automethod:: pybricks.hubs::EssentialHub.imu.ready
.. automethod:: pybricks.hubs::EssentialHub.imu.stationary
.. automethod:: pybricks.hubs::EssentialHub.imu.up
.. automethod:: pybricks.hubs::EssentialHub.imu.tilt
@@ -37,6 +41,22 @@ Essential Hub
.. automethod:: pybricks.hubs::EssentialHub.imu.reset_heading
.. automethod:: pybricks.hubs::EssentialHub.imu.rotation
.. automethod:: pybricks.hubs::EssentialHub.imu.orientation
.. automethod:: pybricks.hubs::EssentialHub.imu.settings
.. rubric:: Using connectionless Bluetooth messaging
.. automethod:: pybricks.hubs::EssentialHub.ble.broadcast
.. automethod:: pybricks.hubs::EssentialHub.ble.observe
.. automethod:: pybricks.hubs::EssentialHub.ble.signal_strength
.. automethod:: pybricks.hubs::EssentialHub.ble.version
.. rubric:: Using the battery
.. automethod:: pybricks.hubs::EssentialHub.battery.voltage
@@ -126,6 +146,23 @@ Reading acceleration and angular velocity on one axis
.. literalinclude::
../../../examples/pup/hub_common/build/imu_read_scalar_essentialhub.py
Bluetooth examples
------------------
Broadcasting data to other hubs
*******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_broadcast_essentialhub.py
Observing data from other hubs
******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_observe_essentialhub.py
System examples
----------------------------------
+27
View File
@@ -31,6 +31,16 @@ Move Hub
Changed acceleration units from m/s² to mm/s².
.. rubric:: Using connectionless Bluetooth messaging
.. automethod:: pybricks.hubs::MoveHub.ble.broadcast
.. automethod:: pybricks.hubs::MoveHub.ble.observe
.. automethod:: pybricks.hubs::MoveHub.ble.signal_strength
.. automethod:: pybricks.hubs::MoveHub.ble.version
.. rubric:: Using the battery
.. automethod:: pybricks.hubs::MoveHub.battery.voltage
@@ -85,6 +95,23 @@ Reading acceleration
.. literalinclude::
../../../examples/pup/hub_movehub/imu_read_acceleration.py
Bluetooth examples
------------------
Broadcasting data to other hubs
*******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_broadcast_movehub.py
Observing data from other hubs
******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_observe_movehub.py
Button and system examples
----------------------------------
+37
View File
@@ -59,6 +59,10 @@ Prime Hub / Inventor Hub
.. rubric:: Using the IMU
.. automethod:: pybricks.hubs::PrimeHub.imu.ready
.. automethod:: pybricks.hubs::PrimeHub.imu.stationary
.. automethod:: pybricks.hubs::PrimeHub.imu.up
.. automethod:: pybricks.hubs::PrimeHub.imu.tilt
@@ -71,6 +75,12 @@ Prime Hub / Inventor Hub
.. automethod:: pybricks.hubs::PrimeHub.imu.reset_heading
.. automethod:: pybricks.hubs::PrimeHub.imu.rotation
.. automethod:: pybricks.hubs::PrimeHub.imu.orientation
.. automethod:: pybricks.hubs::PrimeHub.imu.settings
.. rubric:: Using the speaker
.. automethod:: pybricks.hubs::PrimeHub.speaker.volume
@@ -79,6 +89,16 @@ Prime Hub / Inventor Hub
.. automethod:: pybricks.hubs::PrimeHub.speaker.play_notes
.. rubric:: Using connectionless Bluetooth messaging
.. automethod:: pybricks.hubs::PrimeHub.ble.broadcast
.. automethod:: pybricks.hubs::PrimeHub.ble.observe
.. automethod:: pybricks.hubs::PrimeHub.ble.signal_strength
.. automethod:: pybricks.hubs::PrimeHub.ble.version
.. rubric:: Using the battery
.. automethod:: pybricks.hubs::PrimeHub.battery.voltage
@@ -237,6 +257,23 @@ Reading acceleration and angular velocity on one axis
.. literalinclude::
../../../examples/pup/hub_common/build/imu_read_scalar_primehub.py
Bluetooth examples
------------------
Broadcasting data to other hubs
*******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_broadcast_primehub.py
Observing data from other hubs
******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_observe_primehub.py
System examples
----------------------------------
+37
View File
@@ -21,6 +21,10 @@ Technic Hub
.. rubric:: Using the IMU
.. automethod:: pybricks.hubs::TechnicHub.imu.ready
.. automethod:: pybricks.hubs::TechnicHub.imu.stationary
.. automethod:: pybricks.hubs::TechnicHub.imu.up
.. automethod:: pybricks.hubs::TechnicHub.imu.tilt
@@ -33,6 +37,22 @@ Technic Hub
.. automethod:: pybricks.hubs::TechnicHub.imu.reset_heading
.. automethod:: pybricks.hubs::TechnicHub.imu.rotation
.. automethod:: pybricks.hubs::TechnicHub.imu.orientation
.. automethod:: pybricks.hubs::TechnicHub.imu.settings
.. rubric:: Using connectionless Bluetooth messaging
.. automethod:: pybricks.hubs::TechnicHub.ble.broadcast
.. automethod:: pybricks.hubs::TechnicHub.ble.observe
.. automethod:: pybricks.hubs::TechnicHub.ble.signal_strength
.. automethod:: pybricks.hubs::TechnicHub.ble.version
.. rubric:: Using the battery
.. automethod:: pybricks.hubs::TechnicHub.battery.voltage
@@ -118,6 +138,23 @@ Reading acceleration and angular velocity on one axis
.. literalinclude::
../../../examples/pup/hub_common/build/imu_read_scalar_technichub.py
Bluetooth examples
------------------
Broadcasting data to other hubs
*******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_broadcast_technichub.py
Observing data from other hubs
******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_observe_technichub.py
Button and system examples
----------------------------------
-1
View File
@@ -60,7 +60,6 @@ above to reveal this menu.
parameters/index
tools/index
robotics
geometry
signaltypes
.. toctree::
+4
View File
@@ -72,6 +72,10 @@ Sequences
.. pybricks-requirements:: stm32-extra
.. autoclass:: ubuiltins.set
.. pybricks-requirements:: stm32-extra
.. autoclass:: ubuiltins.slice
.. pybricks-requirements::
+1 -1
View File
@@ -38,7 +38,7 @@ Powers and logarithms
.. autofunction:: umath.sqrt
Trigonomety
Trigonometry
-------------------------------
.. autodata:: umath.pi
+7
View File
@@ -0,0 +1,7 @@
.. pybricks-requirements:: stm32-float
Axis
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: pybricks.parameters.Axis
:no-members:
+3
View File
@@ -10,6 +10,7 @@
:maxdepth: 1
:hidden:
axis
button
color
direction
@@ -18,6 +19,8 @@
side
stop
.. pybricks-classlink:: Axis
.. pybricks-classlink:: Button
.. pybricks-classlink:: Color
+10 -2
View File
@@ -18,12 +18,12 @@ Motors with rotation sensors
.. rubric:: Measuring
.. automethod:: pybricks.pupdevices.Motor.speed
.. automethod:: pybricks.pupdevices.Motor.angle
.. automethod:: pybricks.pupdevices.Motor.reset_angle
.. automethod:: pybricks.pupdevices.Motor.speed
.. automethod:: pybricks.pupdevices.Motor.load
.. automethod:: pybricks.pupdevices.Motor.stalled
@@ -93,6 +93,14 @@ Motors with rotation sensors
The :meth:`done`, :meth:`stalled` and :meth:`load` methods have been
moved.
.. pybricks-requirements:: pybricks-common-control
.. automethod:: pybricks.pupdevices.Motor.model.state
.. pybricks-requirements:: pybricks-common-control
.. automethod:: pybricks.pupdevices.Motor.model.settings
Initialization examples
-----------------------
+35 -5
View File
@@ -1,11 +1,10 @@
.. pybricks-requirements::
:mod:`robotics <pybricks.robotics>` -- Robotics and drive bases
===============================================================
.. automodule:: pybricks.robotics
:no-members:
.. pybricks-requirements::
.. autoclass:: pybricks.robotics.DriveBase
:no-members:
@@ -40,6 +39,8 @@
.. automethod:: pybricks.robotics.DriveBase.stop
.. automethod:: pybricks.robotics.DriveBase.brake
.. rubric:: Measuring
.. automethod:: pybricks.robotics.DriveBase.distance
@@ -52,6 +53,33 @@
.. automethod:: pybricks.robotics.DriveBase.stalled
.. rubric:: Driving with the gyro
.. automethod:: pybricks.robotics.DriveBase.use_gyro
If your hub is not mounted flat in your robot, make sure to specify
the ``top_side`` and ``front_side`` parameters when you initialize the
:class:`PrimeHub() <pybricks.hubs.PrimeHub>`,
:class:`InventorHub() <pybricks.hubs.PrimeHub>`,
:class:`EssentialHub() <pybricks.hubs.EssentialHub>`, or
:class:`TechnicHub() <pybricks.hubs.TechnicHub>`. This way your robot
knows which rotation to measure when turning.
The gyro in each hub is a bit different, which can cause it to be a few
degrees off for big turns, or many small turns in the same
direction. For example, you may need to use
:meth:`turn(357) <pybricks.robotics.DriveBase.turn>` or
:meth:`turn(362) <pybricks.robotics.DriveBase.turn>`
on your robot to make a full turn.
By default, this class tries to maintain the robot's position after a move
completes. This means the wheels will spin if you pick the robot up, in an
effort to maintain its heading angle. To avoid this, you can choose
``then=Stop.COAST`` in your last
:meth:`straight <pybricks.robotics.DriveBase.straight>`,
:meth:`turn <pybricks.robotics.DriveBase.turn>`, or
:meth:`curve <pybricks.robotics.DriveBase.curve>` command.
.. _measuring:
.. rubric:: Measuring and validating the robot dimensions
@@ -103,9 +131,6 @@
the default speed and acceleration for straight maneuvers and turns.
Use the following attributes to adjust more advanced control settings.
You can only change the settings while the robot is stopped. This is
either before you begin driving or after you call :meth:`.stop`.
.. autoattribute:: pybricks.robotics.DriveBase.distance_control
:annotation:
@@ -116,11 +141,16 @@
The :meth:`done` and :meth:`stalled` methods have been moved.
.. pybricks-requirements:: gyro
Examples
-------------------
Driving straight and turning in place
**********************************************
The following program shows the basics of driving and turning.
.. literalinclude::
../../examples/pup/robotics/drivebase_basics.py
+34 -1
View File
@@ -248,7 +248,7 @@ For example, you can choose the frequency of a beep to change the pitch.
temperature: °C
---------------
Temperature is measured in degrees Celcius (°C). To convert to degrees
Temperature is measured in degrees Celsius (°C). To convert to degrees
Fahrenheit (°F) or Kelvin (K), you can use the following conversion formulas:
:math:`^{\circ}\kern1pt\!F =\kern1pt^{\circ}\kern1pt\!C \cdot \frac{9}{5} + 32`.
@@ -260,3 +260,36 @@ Fahrenheit (°F) or Kelvin (K), you can use the following conversion formulas:
hue: deg
--------------
Hue of a color (0-359 degrees).
.. _robotframe:
Reference frames
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Pybricks module and this documentation use the following conventions:
- X: Positive means forward. Negative means backward.
- Y: Positive means to the left. Negative means to the right.
- Z: Positive means upward. Negative means downward.
To make sure that all hub measurements (such as acceleration) have the correct
value and sign, you can specify how the hub is mounted in your creation. This
adjust the measurements so that it is easy to see how your *robot* is moving,
rather than how the *hub* is moving.
For example, the hub may be mounted upside down in your design. If you
configure the settings as shown in :numref:`fig_imuexamples`, the hub
measurements will be adjusted accordingly. This way, a positive acceleration
value in the X direction means that your *robot* accelerates forward, even
though the *hub* accelerates backward.
.. _fig_imuexamples:
.. figure:: ../main/diagrams/imuexamples.png
:width: 100 %
How to configure the ``top_side`` and ``front_side`` settings for three
different robot designs. The same technique can be applied to other hubs
and other creations, by noting which way the top and
front :class:`Side <Side>` of the hub are pointing. The example
on the left is the default configuration.
+69 -1
View File
@@ -1,11 +1,14 @@
.. pybricks-requirements::
:mod:`tools <pybricks.tools>` -- Timing tools
:mod:`tools <pybricks.tools>` -- General purpose tools
========================================================
.. automodule:: pybricks.tools
:no-members:
Timing tools
---------------
.. autofunction:: wait
.. autoclass:: pybricks.tools.StopWatch
@@ -19,3 +22,68 @@
.. automethod:: pybricks.tools.StopWatch.reset
Input tools
-----------
.. autofunction:: pybricks.tools.read_input_byte
.. pybricks-requirements:: light-matrix
.. autofunction:: pybricks.tools.hub_menu
.. literalinclude::
../../../examples/pup/tools/hub_menu.py
Linear algebra tools
--------------------
.. versionchanged:: 3.3
These tools were previously located in the ``pybricks.geometry`` module.
.. pybricks-requirements:: stm32-float
.. autoclass:: pybricks.tools.Matrix
:no-members:
.. autoattribute:: pybricks.tools::Matrix.T
.. autoattribute:: pybricks.tools::Matrix.shape
.. pybricks-requirements:: stm32-float
.. autofunction:: pybricks.tools.vector
.. autofunction:: pybricks.tools.cross
Multitasking
--------------------
.. versionadded:: 3.3
Pybricks supports cooperative multitasking using the ``async`` and ``await``
keywords. This allows operations that normally take some time to complete to
run in parallel with other operations.
.. autofunction:: pybricks.tools.multitask
.. autofunction:: pybricks.tools.run_task
The following example shows how to use multitasking to make a robot drive
forward, then turn and move a gripper at the same time, and then drive
backward.
.. literalinclude::
../../../examples/pup/robotics/drivebase_async.py
.. class:: coroutine
.. class:: await
Whenever you see a function or method prefixed by ``await``, this means that
it supports multitasking. When running a coroutine with ``run_task``, all
methods and functions prefixed by ``await`` will act as coroutines.
If you don't use multitasking, you can ignore the ``await`` keyword and write
programs as usual. Specifically, when ``run_task`` is not used, functions
prefixed by ``await`` will act as normal functions.
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
from pybricks.messaging import BluetoothMailboxServer, TextMailbox
# This demo makes your PC talk to an EV3 over Bluetooth.
#
# This is identical to the EV3 server example in ../bluetooth_server
#
# The only difference is that it runs in Python3 on your computer, thanks to
# the Python3 implementation of the messaging module that is included here.
# As far as the EV3 is concerned, it thinks it just talks to an EV3 client.
#
# So, the EV3 client example needs no further modifications. The connection
# procedure is also the same as documented in the messaging module docs:
# https://docs.pybricks.com/en/latest/messaging.html
server = BluetoothMailboxServer()
mbox = TextMailbox("greeting", server)
# The server must be started before the client!
print("waiting for connection...")
server.wait_for_connection()
print("connected!")
# In this program, the server waits for the client to send the first message
# and then sends a reply.
mbox.wait()
print(mbox.read())
mbox.send("hello to you!")
+16 -56
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 The Pybricks Authors
# Copyright (C) 2020,2023 The Pybricks Authors
"""
:class:`RFCOMMServer` can be used to communicate with other Bluetooth RFCOMM
@@ -10,29 +10,13 @@ remain a strict subset of that implementation when it comes to low-level
implementation details.
"""
from bluetooth import BluetoothSocket, RFCOMM
from socket import socket, AF_BLUETOOTH, BTPROTO_RFCOMM, SOCK_STREAM
from socketserver import ThreadingMixIn
BDADDR_ANY = ""
def str2ba(string, ba):
"""Convert string to Bluetooth address"""
for i, v in enumerate(string.split(":")):
ba.b[5 - i] = int(v, 16)
def ba2str(ba):
"""Convert Bluetooth address to string"""
string = []
for b in ba.b:
string.append("{:02X}".format(b))
string.reverse()
return ":".join(string).upper()
class RFCOMMServer:
"""Object that simplifies setting up an RFCOMM socket server.
"""
Object that simplifies setting up an RFCOMM socket server.
This is based on the ``socketserver.SocketServer`` class in the Python
standard library.
@@ -44,10 +28,10 @@ class RFCOMMServer:
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass
self.socket = BluetoothSocket(RFCOMM)
self.socket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)
try:
self.socket.bind((server_address[0], server_address[1]))
self.socket.bind(server_address)
# self.server_address = self.socket.getsockname()
self.socket.listen(self.request_queue_size)
except Exception:
@@ -83,50 +67,22 @@ class RFCOMMServer:
self.socket.close()
class StreamRequestHandler:
"""Class that handles incoming requests.
This is based on ``socketserver.StreamRequestHandler`` from the Python
standard library.
"""
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
def setup(self):
self.wfile = self.request
self.rfile = self.request
def handle(self):
pass
def finish(self):
pass
class ThreadingRFCOMMServer(ThreadingMixIn, RFCOMMServer):
"""Version of :class:`RFCOMMServer` that handles connections in a new
thread.
"""
Version of :class:`RFCOMMServer` that handles connections in a new thread.
"""
pass
daemon_threads = True
class RFCOMMClient:
def __init__(self, client_address, RequestHandlerClass):
self.client_address = client_address
self.RequestHandlerClass = RequestHandlerClass
self.socket = BluetoothSocket(RFCOMM)
self.socket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)
def handle_request(self):
self.socket.connect((self.client_address[0], self.client_address[1]))
self.socket.connect(self.client_address)
try:
self.process_request(self.socket, self.client_address)
except Exception:
@@ -145,4 +101,8 @@ class RFCOMMClient:
class ThreadingRFCOMMClient(ThreadingMixIn, RFCOMMClient):
pass
"""
Version of :class:`RFCOMMClient` that handles connections in a new thread.
"""
daemon_threads = True
+11 -14
View File
@@ -1,16 +1,13 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 The Pybricks Authors
# Copyright (C) 2020,2023 The Pybricks Authors
from _thread import allocate_lock
from errno import ECONNRESET
from struct import pack, unpack
from socket import BDADDR_ANY
from socketserver import StreamRequestHandler
from threading import Lock
from .bluetooth import (
BDADDR_ANY,
ThreadingRFCOMMServer,
ThreadingRFCOMMClient,
StreamRequestHandler,
)
from .bluetooth import ThreadingRFCOMMServer, ThreadingRFCOMMClient
def resolve(brick):
@@ -151,7 +148,7 @@ class MailboxHandler(StreamRequestHandler):
self.server._clients[self.client_address[0]] = self.request
while True:
try:
buf = self.rfile.recv(2)
buf = self.rfile.read(2)
if len(buf) == 0:
break
except OSError as ex:
@@ -160,7 +157,7 @@ class MailboxHandler(StreamRequestHandler):
break
raise
(size,) = unpack("<H", buf)
buf = self.rfile.recv(size)
buf = self.rfile.read(size)
msg_count, cmd_type, cmd, name_size = unpack("<HBBB", buf[0:5])
if cmd_type != SYSTEM_COMMAND_NO_REPLY:
raise ValueError("Bad message type")
@@ -180,7 +177,7 @@ class MailboxHandler(StreamRequestHandler):
class MailboxHandlerMixIn:
def __init__(self):
# protects against concurrent access of other attributes
self._lock = allocate_lock()
self._lock = Lock()
# map of mailbox name to raw data
self._mailboxes = {}
# map of device name/address to object with send() method
@@ -247,7 +244,7 @@ class MailboxHandlerMixIn:
def wait_for_mailbox_update(self, mbox):
"""Waits until ``mbox`` receives a value."""
lock = allocate_lock()
lock = Lock()
lock.acquire()
with self._lock:
self._updates[mbox] = lock
@@ -264,7 +261,7 @@ class BluetoothMailboxServer(MailboxHandlerMixIn, ThreadingRFCOMMServer):
EV3.
The remote EV3 can either be running MicroPython or the standard EV3
firmare.
firmware.
"""
super().__init__()
super(ThreadingRFCOMMServer, self).__init__(
@@ -307,7 +304,7 @@ class BluetoothMailboxClient(MailboxHandlerMixIn):
remote EV3s.
The remote EV3s can either be running MicroPython or the standard EV3
firmare.
firmware.
"""
def __enter__(self):
+1 -1
View File
@@ -36,7 +36,7 @@ def scale(val, src, dst):
# Create a loop to react to events
# This loop reacte to all main PS4 button and stick events. I have left out
# This loop reacts to all main PS4 button and stick events. I have left out
# buttons like share and options, but can easily be added in by referring
# to the table at: https://github.com/codeadamca/python-connect-ps4
+25
View File
@@ -0,0 +1,25 @@
# ThisHub = MoveHub CityHub TechnicHub PrimeHub EssentialHub
from pybricks.hubs import ThisHub
from pybricks.pupdevices import Motor
from pybricks.parameters import Port
from pybricks.tools import wait
# Initialize the hub.
hub = ThisHub(broadcast_channel=1)
# Initialize the motors.
left_motor = Motor(Port.A)
right_motor = Motor(Port.B)
while True:
# Read the motor angles to be sent to the other hub.
left_angle = left_motor.angle()
right_angle = right_motor.angle()
# Set the broadcast data and start broadcasting if not already doing so.
data = (left_angle, right_angle)
hub.ble.broadcast(data)
# Broadcasts are only sent every 100 milliseconds, so there is no reason
# to call the broadcast() method more often than that.
wait(100)
+38
View File
@@ -0,0 +1,38 @@
# ThisHub = MoveHub CityHub TechnicHub PrimeHub EssentialHub
from pybricks.hubs import ThisHub
from pybricks.pupdevices import Motor
from pybricks.parameters import Color, Port
from pybricks.tools import wait
# Initialize the hub.
hub = ThisHub(observe_channels=[1])
# Initialize the motors.
left_motor = Motor(Port.A)
right_motor = Motor(Port.B)
while True:
# Receive broadcast from the other hub.
data = hub.ble.observe(1)
if data is None:
# No data has been received in the last 1 second.
hub.light.on(Color.RED)
else:
# Data was received and is less that one second old.
hub.light.on(Color.GREEN)
# *data* contains the same values in the same order
# that were passed to hub.ble.broadcast() on the
# other hub.
left_angle, right_angle = data
# Make the motors on this hub mirror the position of the
# motors on the other hub.
left_motor.track_target(left_angle)
right_motor.track_target(right_angle)
# Broadcasts are only sent every 100 milliseconds, so there is
# no reason to call the observe() method more often than that.
wait(100)
+1 -1
View File
@@ -1,7 +1,7 @@
# ThisHub = TechnicHub PrimeHub EssentialHub
from pybricks.hubs import ThisHub
from pybricks.tools import wait
from pybricks.geometry import Axis
from pybricks.parameters import Axis
# Initialize the hub.
hub = ThisHub()
+1 -1
View File
@@ -1,7 +1,7 @@
# ThisHub = TechnicHub PrimeHub EssentialHub
from pybricks.hubs import ThisHub
from pybricks.tools import wait
from pybricks.geometry import Axis
from pybricks.parameters import Axis
# Initialize the hub. In this case, specify that the hub is mounted with the
# top side facing forward and the front side facing to the right.
+1 -2
View File
@@ -1,6 +1,5 @@
from pybricks.hubs import PrimeHub
from pybricks.tools import wait
from pybricks.geometry import Matrix
from pybricks.tools import wait, Matrix
# Initialize the hub.
hub = PrimeHub()
+21 -6
View File
@@ -4,19 +4,34 @@ from uerrno import ENODEV
# Dictionary of device identifiers along with their name.
device_names = {
34: "Wedo 2.0 Tilt Sensor",
35: "Wedo 2.0 Infrared Sensor",
37: "BOOST Color Distance Sensor",
# pybricks.pupdevices.DCMotor
1: "Wedo 2.0 Medium Motor",
2: "Powered Up Train Motor",
# pybricks.pupdevices.Light
8: "Powered Up Light",
# pybricks.pupdevices.Motor
38: "BOOST Interactive Motor",
46: "Technic Large Motor",
47: "Technic Extra Large Motor",
48: "SPIKE Medium Angular Motor",
49: "SPIKE Large Angular Motor",
61: "SPIKE Color Sensor",
62: "SPIKE Ultrasonic Sensor",
63: "SPIKE Force Sensor",
65: "SPIKE Small Angular Motor",
75: "Technic Medium Angular Motor",
76: "Technic Large Angular Motor",
# pybricks.pupdevices.TiltSensor
34: "Wedo 2.0 Tilt Sensor",
# pybricks.pupdevices.InfraredSensor
35: "Wedo 2.0 Infrared Motion Sensor",
# pybricks.pupdevices.ColorDistanceSensor
37: "BOOST Color Distance Sensor",
# pybricks.pupdevices.ColorSensor
61: "SPIKE Color Sensor",
# pybricks.pupdevices.UltrasonicSensor
62: "SPIKE Ultrasonic Sensor",
# pybricks.pupdevices.ForceSensor
63: "SPIKE Force Sensor",
# pybricks.pupdevices.ColorLightMatrix
64: "SPIKE 3x3 Color Light Matrix",
}
# Make a list of known ports.
+27
View File
@@ -0,0 +1,27 @@
from pybricks.pupdevices import Motor
from pybricks.parameters import Direction, Port
from pybricks.robotics import DriveBase
from pybricks.tools import multitask, run_task
# Set up all devices.
left = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right = Motor(Port.B)
gripper = Motor(Port.C)
drive_base = DriveBase(left, right, 56, 114)
# Move the gripper up and down.
async def move_gripper():
await gripper.run_angle(500, -90)
await gripper.run_angle(500, 90)
# Drive forward, turn move gripper at the same time, and drive backward.
async def main():
await drive_base.straight(250)
await multitask(drive_base.turn(90), move_gripper())
await drive_base.straight(-250)
# Runs the main program from start to finish.
run_task(main())
+5 -2
View File
@@ -11,13 +11,16 @@ right_motor = Motor(Port.B)
# The distance between the two wheel-ground contact points is 112mm.
drive_base = DriveBase(left_motor, right_motor, wheel_diameter=56, axle_track=112)
# Optionally, uncomment the line below to use the gyro for improved accuracy.
# drive_base.use_gyro(True)
# Drive forward by 500mm (half a meter).
drive_base.straight(500)
# Turn around clockwise (180 degrees)
# Turn around clockwise by 180 degrees.
drive_base.turn(180)
# Drive forward again to drive back.
# Drive forward again to get back to the start.
drive_base.straight(500)
# Turn around counterclockwise.
+16
View File
@@ -0,0 +1,16 @@
from pybricks.tools import hub_menu
# This example assumes that you have three other programs in Pybricks Code,
# called "fly_mission", "drive_mission", and "zigzag". This example creates a
# menu that lets you pick which one to run.
# Choose a letter.
selected = hub_menu("F", "D", "Z")
# Based on the selection, run a program.
if selected == "F":
import fly_mission
elif selected == "D":
import drive_mission
elif selected == "Z":
import zigzag
+24
View File
@@ -4,6 +4,30 @@
## Unreleased
## 1.10.0 - 2023-10-26
### Changed
- Updated `pybricks` package to v3.3.0b9.
## 1.9.0 - 2023-05-16
### Changed
- Updated `pybricks` package to v3.3.0b5.
## 1.8.0 - 2023-04-21
### Changed
- Updated `pybricks` package to v3.3.0b4.
## 1.7.0 - 2022-12-28
### Added
- Added `update_user_modules()` function for filtering on user modules.
### Fixed
- Fixed code completion for builtin types.
- Fixed code completion for names starting with `_`.
## 1.6.0 - 2022-12-09
### Changed
+114 -160
View File
@@ -1,24 +1,26 @@
[[package]]
name = "attrs"
version = "22.1.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=3.5"
[package.extras]
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
# This file is automatically @generated by Poetry and should not be changed by hand.
[[package]]
name = "black"
version = "22.10.0"
version = "22.12.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"},
{file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"},
{file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"},
{file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"},
{file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"},
{file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"},
{file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"},
{file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"},
{file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"},
{file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"},
{file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"},
{file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"},
]
[package.dependencies]
click = ">=8.0.0"
@@ -35,11 +37,15 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "click"
version = "8.1.3"
version = "8.1.7"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
@@ -51,6 +57,10 @@ description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "docstring-parser"
@@ -59,14 +69,22 @@ description = "Parse Python docstrings in reST, Google and Numpydoc format"
category = "main"
optional = false
python-versions = ">=3.6,<4.0"
files = [
{file = "docstring_parser-0.14.1-py3-none-any.whl", hash = "sha256:14ac6ec1f1ba6905c4d8cb90fd0bc55394f5678183752c90e44812bf28d7a515"},
{file = "docstring_parser-0.14.1.tar.gz", hash = "sha256:2c77522e31b7c88b1ab457a1f3c9ae38947ad719732260ba77ee8a3deb58622a"},
]
[[package]]
name = "exceptiongroup"
version = "1.0.4"
version = "1.1.3"
description = "Backport of PEP 654 (exception groups)"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
{file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
]
[package.extras]
test = ["pytest (>=6)"]
@@ -78,6 +96,10 @@ description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
]
[package.dependencies]
mccabe = ">=0.6.0,<0.7.0"
@@ -86,11 +108,15 @@ pyflakes = ">=2.4.0,<2.5.0"
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "jedi"
@@ -99,6 +125,10 @@ description = "An autocompletion tool for Python that can be used for text edito
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
]
[package.dependencies]
parso = ">=0.8.0,<0.9.0"
@@ -114,25 +144,34 @@ description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
python-versions = "*"
files = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false
python-versions = "*"
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "21.3"
version = "23.2"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
python-versions = ">=3.7"
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
]
[[package]]
name = "parso"
@@ -141,6 +180,10 @@ description = "A Python Parser"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
]
[package.extras]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
@@ -148,31 +191,43 @@ testing = ["docopt", "pytest (<6.0.0)"]
[[package]]
name = "pathspec"
version = "0.10.2"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "platformdirs"
version = "2.5.4"
version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
]
[package.extras]
docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
version = "1.0.0"
version = "1.3.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.8"
files = [
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
]
[package.extras]
dev = ["pre-commit", "tox"]
@@ -180,11 +235,12 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pybricks"
version = "3.2.0c1"
version = "3.3.0b9"
description = "Documentation and user-API stubs for Pybricks MicroPython"
category = "main"
optional = false
python-versions = "^3.8"
files = []
develop = true
[package.source]
@@ -198,6 +254,10 @@ description = "Python style guide checker"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
]
[[package]]
name = "pyflakes"
@@ -206,28 +266,24 @@ description = "passive checker of Python programs"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
files = [
{file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
]
[[package]]
name = "pytest"
version = "7.2.0"
version = "7.4.3"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
{file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
]
[package.dependencies]
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
@@ -236,7 +292,7 @@ pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "tomli"
@@ -245,6 +301,10 @@ description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "typing-extensions"
@@ -253,118 +313,12 @@ description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
python-versions = ">=3.7"
[metadata]
lock-version = "1.1"
python-versions = ">= 3.10, < 3.11"
content-hash = "971c6d0dddd9bb8e33a942e2360905a7d80a1a9d33015c0defc08345df5c1019"
[metadata.files]
attrs = [
{file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
{file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
]
black = [
{file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
{file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
{file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
{file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
{file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
{file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
{file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
{file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
{file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
{file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
{file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
{file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
{file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
{file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
{file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
{file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
{file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
{file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
{file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
]
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
colorama = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
docstring-parser = [
{file = "docstring_parser-0.14.1-py3-none-any.whl", hash = "sha256:14ac6ec1f1ba6905c4d8cb90fd0bc55394f5678183752c90e44812bf28d7a515"},
{file = "docstring_parser-0.14.1.tar.gz", hash = "sha256:2c77522e31b7c88b1ab457a1f3c9ae38947ad719732260ba77ee8a3deb58622a"},
]
exceptiongroup = [
{file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
{file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
]
flake8 = [
{file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
{file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
jedi = [
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
parso = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
]
pathspec = [
{file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"},
{file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"},
]
platformdirs = [
{file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
{file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
pybricks = []
pycodestyle = [
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
]
pyflakes = [
{file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
{file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
{file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
typing-extensions = [
files = [
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
]
[metadata]
lock-version = "2.0"
python-versions = ">= 3.10, < 3.12"
content-hash = "54c3f61c40e3890ef4395e3a2b1a603938b07aa7f4ea1e41ce22d5e180d634e8"
+3 -3
View File
@@ -1,13 +1,13 @@
[tool.poetry]
name = "pybricks_jedi"
version = "1.6.0"
version = "1.10.0"
description = "Code completion for Pybricks."
authors = ["The Pybricks Authors <team@pybricks.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = ">= 3.10, < 3.11"
pybricks = "3.2.0c1"
python = ">= 3.10, < 3.12"
pybricks = "3.3.0b9"
jedi = "0.18.1"
typing-extensions = "4.2.0"
docstring-parser = "0.14.1"
+171 -90
View File
@@ -2,6 +2,7 @@ import io
import json
import re
from enum import IntEnum
from typing import Iterable
import docstring_parser
import jedi
@@ -12,7 +13,6 @@ from typing_extensions import NotRequired, TypedDict
PYBRICKS_CODE_PACKAGES = {
"micropython",
"pybricks",
"pybricks.geometry",
"pybricks.hubs",
"pybricks.iodevices",
"pybricks.parameters",
@@ -31,90 +31,153 @@ PYBRICKS_CODE_PACKAGES = {
# Subset of Python builtins included in Pybricks MicroPython.
PYBRICKS_BUILTINS = {
"abs",
"all",
"any",
"ArithmeticError",
"AssertionError",
"AttributeError",
"BaseException",
"bin",
"bool",
"bytearray",
"bytes",
"callable",
"chr",
"classmethod",
"complex",
"dict",
"dir",
"divmod",
"enumerate",
"EOFError",
"eval",
"Exception",
"exec",
"float",
"GeneratorExit",
"getattr",
"globals",
"hasattr",
"hash",
"help",
"hex",
"id",
"ImportError",
"IndentationError",
"IndexError",
"input",
"int",
"isinstance",
"issubclass",
"iter",
"KeyboardInterrupt",
"KeyError",
"len",
"list",
"locals",
"LookupError",
"map",
"max",
"MemoryError",
"min",
"NameError",
"next",
"NotImplementedError",
"object",
"oct",
"ord",
"OSError",
"OverflowError",
"pow",
"print",
"range",
"repr",
"reversed",
"round",
"RuntimeError",
"set",
"setattr",
"slice",
"sorted",
"staticmethod",
"StopIteration",
"str",
"sum",
"super",
"SyntaxError",
"SystemExit",
"tuple",
"type",
"TypeError",
"ValueError",
"ZeroDivisionError",
"zip",
"builtins.abs",
"builtins.all",
"builtins.any",
"builtins.ArithmeticError",
"builtins.AssertionError",
"builtins.AttributeError",
"builtins.BaseException",
"builtins.bin",
"builtins.bool",
"builtins.bytearray",
"builtins.bytes",
"builtins.callable",
"builtins.chr",
"builtins.classmethod",
"builtins.complex",
"builtins.dict",
"builtins.dir",
"builtins.divmod",
"builtins.enumerate",
"builtins.EOFError",
"builtins.eval",
"builtins.Exception",
"builtins.exec",
"builtins.float",
"builtins.GeneratorExit",
"builtins.getattr",
"builtins.globals",
"builtins.hasattr",
"builtins.hash",
"builtins.help",
"builtins.hex",
"builtins.id",
"builtins.ImportError",
"builtins.IndentationError",
"builtins.IndexError",
"builtins.input",
"builtins.int",
"builtins.isinstance",
"builtins.issubclass",
"builtins.iter",
"builtins.KeyboardInterrupt",
"builtins.KeyError",
"builtins.len",
"builtins.list",
"builtins.locals",
"builtins.LookupError",
"builtins.map",
"builtins.max",
"builtins.MemoryError",
"builtins.min",
"builtins.NameError",
"builtins.next",
"builtins.NotImplementedError",
"builtins.object",
"builtins.oct",
"builtins.ord",
"builtins.OSError",
"builtins.OverflowError",
"builtins.pow",
"builtins.print",
"builtins.range",
"builtins.repr",
"builtins.reversed",
"builtins.round",
"builtins.RuntimeError",
"builtins.set",
"builtins.setattr",
"builtins.slice",
"builtins.sorted",
"builtins.staticmethod",
"builtins.StopIteration",
"builtins.str",
"builtins.sum",
"builtins.super",
"builtins.SyntaxError",
"builtins.SystemExit",
"builtins.tuple",
"builtins.type",
"builtins.TypeError",
"builtins.ValueError",
"builtins.ZeroDivisionError",
"builtins.zip",
"builtins.bytearray.append",
"builtins.bytearray.extend",
"builtins.dict.clear",
"builtins.dict.copy",
"builtins.dict.fromkeys",
"builtins.dict.get",
"builtins.dict.items",
"builtins.dict.keys",
"builtins.dict.pop",
"builtins.dict.popitem",
"builtins.dict.setdefault",
"builtins.dict.update",
"builtins.dict.values",
"builtins.int.from_bytes",
"builtins.int.to_bytes",
"builtins.list.append",
"builtins.list.clear",
"builtins.list.copy",
"builtins.list.count",
"builtins.list.extend",
"builtins.list.index",
"builtins.list.insert",
"builtins.list.pop",
"builtins.list.remove",
"builtins.list.reverse",
"builtins.list.sort",
"builtins.str.count",
"builtins.str.endswith",
"builtins.str.find",
"builtins.str.format",
"builtins.str.index",
"builtins.str.isalpha",
"builtins.str.isdigit",
"builtins.str.islower",
"builtins.str.isspace",
"builtins.str.isupper",
"builtins.str.join",
"builtins.str.lower",
"builtins.str.lstrip",
"builtins.str.replace",
"builtins.str.rfind",
"builtins.str.rindex",
"builtins.str.rsplit",
"builtins.str.rstrip",
"builtins.str.split",
"builtins.str.startswith",
"builtins.str.strip",
"builtins.str.upper",
"builtins.tuple.count",
"builtins.tuple.index",
}
PYBRICKS_BUILTINS_NO_FULLNAME = {"items", "values"}
PYBRICKS_TYPING = {
"typing.MutableSequence.append",
"typing.MutableSequence.extend",
"typing.MutableMapping.pop",
"typing.Mapping.get",
}
MICROPY_NOT_SUPPORTED_DUNDER = {"__doc__", "__package__"}
user_modules = set()
# Types from monaco editor
@@ -243,10 +306,14 @@ class SignatureHelp(TypedDict):
def _is_pybricks(c: Completion) -> bool:
# filter all "private" names (leading underscore)
if (isinstance(c.name, str)) and c.name.startswith("_"):
return False
if c.name is not None:
if c.name.startswith("_") and c.module_name != "__main__":
return False
if isinstance(c.full_name, str):
if c.name in MICROPY_NOT_SUPPORTED_DUNDER:
return False
if c.full_name is not None:
# this catches things like `from __future__ import annotations`
if c.full_name.startswith("_") and c.module_name != "__main__":
return False
@@ -256,16 +323,19 @@ def _is_pybricks(c: Completion) -> bool:
return False
# filter out typing types
if c.full_name.startswith("typing."):
if c.full_name.startswith("typing.") and c.full_name not in PYBRICKS_TYPING:
return False
# filter out packages/modules that are not included in Pybricks firmware
if c.type == "module" or c.type == "namespace":
return c.full_name in PYBRICKS_CODE_PACKAGES
return c.full_name in PYBRICKS_CODE_PACKAGES or c.full_name in user_modules
# filter subset of builtins
if c.module_name == "builtins" and c.type != "keyword":
return c.name in PYBRICKS_BUILTINS
if c.full_name is None:
return c.name in PYBRICKS_BUILTINS_NO_FULLNAME
return c.full_name in PYBRICKS_BUILTINS
# this is a type alias, not a real type
if c.full_name == "pybricks.parameters.Number":
@@ -418,7 +488,6 @@ def initialize():
"pybricks._common",
"pybricks.ev3dev",
"pybricks.ev3dev.speaker",
"pybricks.geometry",
"pybricks.hubs",
"pybricks.iodevices",
"pybricks.parameters",
@@ -473,3 +542,15 @@ def get_signatures(code: str, line: int, column: int) -> str:
"""
signatures = jedi.Script(code).get_signatures(line, column - 1)
return json.dumps(_map_signatures(signatures))
def update_user_modules(names: Iterable[str]) -> None:
"""
Updates the set of user module names used for filtering.
Args:
names:
An iterable of module names.
"""
user_modules.clear()
user_modules.update(names)
+143
View File
@@ -7,6 +7,9 @@ Tests for correct code completion of builtins.
import json
import pytest
from pybricks_jedi import CompletionItem, complete
@@ -123,4 +126,144 @@ def test_empty_code():
"yield",
"ZeroDivisionError",
"zip",
"__name__",
]
FUNCTION_PARAMS = [
pytest.param(
"''.",
[
"count",
"endswith",
"find",
"format",
"index",
"isalpha",
"isdigit",
"islower",
"isspace",
"isupper",
"join",
"lower",
"lstrip",
"replace",
"rfind",
"rindex",
"rsplit",
"rstrip",
"split",
"startswith",
"strip",
"upper",
],
),
pytest.param(
"str().",
[
"count",
"endswith",
"find",
"format",
"index",
"isalpha",
"isdigit",
"islower",
"isspace",
"isupper",
"join",
"lower",
"lstrip",
"replace",
"rfind",
"rindex",
"rsplit",
"rstrip",
"split",
"startswith",
"strip",
"upper",
],
),
pytest.param("(0).", ["from_bytes", "to_bytes"]),
pytest.param("int().", ["from_bytes", "to_bytes"]),
pytest.param(
"{}.",
[
"clear",
"copy",
"fromkeys",
"get",
"items",
"keys",
"pop",
"popitem",
"setdefault",
"update",
"values",
],
),
pytest.param(
"dict().",
[
"clear",
"copy",
"fromkeys",
"get",
"items",
"keys",
"pop",
"popitem",
"setdefault",
"update",
"values",
],
),
pytest.param(
"[].",
[
"append",
"clear",
"copy",
"count",
"extend",
"index",
"insert",
"pop",
"remove",
"reverse",
"sort",
],
),
pytest.param(
"list().",
[
"append",
"clear",
"copy",
"count",
"extend",
"index",
"insert",
"pop",
"remove",
"reverse",
"sort",
],
),
pytest.param("().", ["count", "index"]),
pytest.param("tuple().", ["count", "index"]),
pytest.param("bytearray().", ["append", "extend"]),
pytest.param("bytes().", []),
pytest.param("b''.", []),
pytest.param("float().", []),
pytest.param("(0.0).", []),
pytest.param("complex().", []),
pytest.param("type().", []),
]
@pytest.mark.parametrize("code,attributes", FUNCTION_PARAMS)
def test_get_completion_for_builtins(code: str, attributes: list[str]):
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == attributes
+1
View File
@@ -33,6 +33,7 @@ def test_hub_dot():
completions: list[CompletionItem] = json.loads(complete(code, 3, len(line) + 1))
assert [c["insertText"] for c in completions] == [
"battery",
"ble",
"button",
"light",
"system",
@@ -33,6 +33,7 @@ def test_hub_dot():
completions: list[CompletionItem] = json.loads(complete(code, 3, len(line) + 1))
assert [c["insertText"] for c in completions] == [
"battery",
"ble",
"button",
"charger",
"imu",
@@ -79,7 +80,12 @@ def test_hub_dot_imu_dot():
"acceleration",
"angular_velocity",
"heading",
"orientation",
"ready",
"reset_heading",
"rotation",
"settings",
"stationary",
"tilt",
"up",
]
+38 -16
View File
@@ -6,7 +6,9 @@ Tests for correct code completion of import statements.
"""
import json
from pybricks_jedi import CompletionItem, complete
import pytest
from pybricks_jedi import CompletionItem, complete, update_user_modules
def test_from():
@@ -27,11 +29,36 @@ def test_from():
]
@pytest.fixture
def user_modules():
update_user_modules(["jedi", "pytest"])
yield
update_user_modules([])
def test_from_with_user_modules(user_modules):
code = "from "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"jedi",
"micropython",
"pybricks",
"pytest",
"uerrno",
"uio",
"ujson",
"umath",
"urandom",
"uselect",
"ustruct",
"usys",
]
def test_from_pybricks_import():
code = "from pybricks import "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"geometry",
"hubs",
"iodevices",
"parameters",
@@ -46,7 +73,6 @@ def test_from_pybricks_dot():
code = "from pybricks."
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"geometry",
"hubs",
"iodevices",
"parameters",
@@ -56,16 +82,6 @@ def test_from_pybricks_dot():
]
def test_from_pybricks_geometry_import():
code = "from pybricks.geometry import "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"Axis",
"Matrix",
"vector",
]
def test_from_pybricks_hubs_import():
code = "from pybricks.hubs import "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
@@ -99,6 +115,7 @@ def test_from_pybricks_parameters_import():
code = "from pybricks.parameters import "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"Axis",
"Button",
"Color",
"Direction",
@@ -131,17 +148,22 @@ def test_from_pybricks_pupdevices_import():
def test_from_pybricks_robotics_import():
code = "from pybricks.robotics import "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"DriveBase",
]
assert [c["insertText"] for c in completions] == ["DriveBase"]
def test_from_pybricks_tools_import():
code = "from pybricks.tools import "
completions: list[CompletionItem] = json.loads(complete(code, 1, len(code) + 1))
assert [c["insertText"] for c in completions] == [
"cross",
"DataLog",
"hub_menu",
"Matrix",
"multitask",
"read_input_byte",
"run_task",
"StopWatch",
"vector",
"wait",
]
+32
View File
@@ -0,0 +1,32 @@
import json
from pybricks_jedi import CompletionItem, complete
def test_get_completion_for_private_globals():
code = """
_X = 0
_
"""
completions: list[CompletionItem] = json.loads(complete(code, 4, 2))
assert [c["insertText"] for c in completions] == ["_X", "__name__"]
def test_get_completion_for_private_attributes():
code = """
class X:
def __init__(self):
self.public = 0
self._protected = 0
self.__private = 0
x = X()
x.
"""
completions: list[CompletionItem] = json.loads(complete(code, 10, 3))
assert [c["insertText"] for c in completions] == [
"public",
"_protected",
"__init__",
]
+1
View File
@@ -33,6 +33,7 @@ def test_hub_dot():
completions: list[CompletionItem] = json.loads(complete(code, 3, len(line) + 1))
assert [c["insertText"] for c in completions] == [
"battery",
"ble",
"button",
"imu",
"light",
+6
View File
@@ -33,6 +33,7 @@ def test_hub_dot():
completions: list[CompletionItem] = json.loads(complete(code, 3, len(line) + 1))
assert [c["insertText"] for c in completions] == [
"battery",
"ble",
"buttons",
"charger",
"display",
@@ -97,7 +98,12 @@ def test_hub_dot_imu_dot():
"acceleration",
"angular_velocity",
"heading",
"orientation",
"ready",
"reset_heading",
"rotation",
"settings",
"stationary",
"tilt",
"up",
]
+6
View File
@@ -33,6 +33,7 @@ def test_hub_dot():
completions: list[CompletionItem] = json.loads(complete(code, 3, len(line) + 1))
assert [c["insertText"] for c in completions] == [
"battery",
"ble",
"button",
"imu",
"light",
@@ -67,7 +68,12 @@ def test_hub_dot_imu_dot():
"acceleration",
"angular_velocity",
"heading",
"orientation",
"ready",
"reset_heading",
"rotation",
"settings",
"stationary",
"tilt",
"up",
]
+191 -52
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2022 The Pybricks Authors
# Copyright (c) 2022-2023 The Pybricks Authors
"""
Tests for correct signatures of the pupdevices.Motor class.
@@ -29,9 +29,10 @@ def _get_function_signature(module: str, function: str) -> SignatureHelp:
FUNCTION_PARAMS = [
pytest.param("pybricks.tools", "wait", [(["time: Number"], "None")]),
pytest.param("pybricks.tools", "read_input_byte", [([], "Optional[int]")]),
pytest.param("pybricks.tools", "wait", [(["time: Number"], "MaybeAwaitable")]),
pytest.param(
"pybricks.geometry",
"pybricks.tools",
"vector",
[
(["x: float", "y: float"], "Matrix"),
@@ -78,17 +79,51 @@ def _get_constructor_signature(module: str, type: str) -> SignatureHelp:
CONSTRUCTOR_PARAMS = [
pytest.param("pybricks.hubs", "MoveHub", [[]]),
pytest.param("pybricks.hubs", "CityHub", [[]]),
pytest.param(
"pybricks.hubs",
"MoveHub",
[["broadcast_channel: int=0", "observe_channels: Sequence[int]=[]"]],
),
pytest.param(
"pybricks.hubs",
"CityHub",
[["broadcast_channel: int=0", "observe_channels: Sequence[int]=[]"]],
),
pytest.param(
"pybricks.hubs",
"TechnicHub",
[["top_side: Axis=Axis.Z", "front_side: Axis=Axis.X"]],
[
[
"top_side: Axis=Axis.Z",
"front_side: Axis=Axis.X",
"broadcast_channel: int=0",
"observe_channels: Sequence[int]=[]",
]
],
),
pytest.param(
"pybricks.hubs",
"PrimeHub",
[["top_side: Axis=Axis.Z", "front_side: Axis=Axis.X"]],
[
[
"top_side: Axis=Axis.Z",
"front_side: Axis=Axis.X",
"broadcast_channel: int=0",
"observe_channels: Sequence[int]=[]",
]
],
),
pytest.param(
"pybricks.hubs",
"EssentialHub",
[
[
"top_side: Axis=Axis.Z",
"front_side: Axis=Axis.X",
"broadcast_channel: int=0",
"observe_channels: Sequence[int]=[]",
]
],
),
pytest.param(
"pybricks.pupdevices",
@@ -104,6 +139,7 @@ CONSTRUCTOR_PARAMS = [
"positive_direction: Direction=Direction.CLOCKWISE",
"gears: Optional[Union[Collection[int], Collection[Collection[int]]]]=None",
"reset_angle: bool=True",
"profile: Number=None",
]
],
),
@@ -151,7 +187,7 @@ CONSTRUCTOR_PARAMS = [
],
),
pytest.param(
"pybricks.geometry",
"pybricks.tools",
"Matrix",
[["rows: Sequence[Sequence[float]]"]],
),
@@ -306,12 +342,19 @@ METHOD_PARAMS = [
[(["axis: Axis"], "float"), ([], "Matrix")],
),
pytest.param("pybricks.hubs", "TechnicHub", "imu.heading", [([], "float")]),
pytest.param("pybricks.hubs", "TechnicHub", "imu.orientation", [([], "Matrix")]),
pytest.param(
"pybricks.hubs",
"TechnicHub",
"imu.reset_heading",
[(["angle: Number"], "None")],
),
pytest.param(
"pybricks.hubs",
"TechnicHub",
"imu.rotation",
[(["axis: Axis"], "float")],
),
pytest.param("pybricks.hubs", "TechnicHub", "battery.voltage", [([], "int")]),
pytest.param("pybricks.hubs", "TechnicHub", "battery.current", [([], "int")]),
pytest.param(
@@ -398,12 +441,19 @@ METHOD_PARAMS = [
[(["axis: Axis"], "float"), ([], "Matrix")],
),
pytest.param("pybricks.hubs", "PrimeHub", "imu.heading", [([], "float")]),
pytest.param("pybricks.hubs", "PrimeHub", "imu.orientation", [([], "Matrix")]),
pytest.param(
"pybricks.hubs",
"PrimeHub",
"imu.reset_heading",
[(["angle: Number"], "None")],
),
pytest.param(
"pybricks.hubs",
"PrimeHub",
"imu.rotation",
[(["axis: Axis"], "float")],
),
pytest.param(
"pybricks.hubs",
"PrimeHub",
@@ -414,13 +464,13 @@ METHOD_PARAMS = [
"pybricks.hubs",
"PrimeHub",
"speaker.beep",
[(["frequency: Number=500", "duration: Number=100"], "None")],
[(["frequency: Number=500", "duration: Number=100"], "MaybeAwaitable")],
),
pytest.param(
"pybricks.hubs",
"PrimeHub",
"speaker.play_notes",
[(["notes: Iterable[str]", "tempo: Number=120"], "None")],
[(["notes: Iterable[str]", "tempo: Number=120"], "MaybeAwaitable")],
),
pytest.param("pybricks.hubs", "PrimeHub", "battery.voltage", [([], "int")]),
pytest.param("pybricks.hubs", "PrimeHub", "battery.current", [([], "int")]),
@@ -481,12 +531,19 @@ METHOD_PARAMS = [
[(["axis: Axis"], "float"), ([], "Matrix")],
),
pytest.param("pybricks.hubs", "EssentialHub", "imu.heading", [([], "float")]),
pytest.param("pybricks.hubs", "EssentialHub", "imu.orientation", [([], "Matrix")]),
pytest.param(
"pybricks.hubs",
"EssentialHub",
"imu.reset_heading",
[(["angle: Number"], "None")],
),
pytest.param(
"pybricks.hubs",
"EssentialHub",
"imu.rotation",
[(["axis: Axis"], "float")],
),
pytest.param("pybricks.hubs", "EssentialHub", "battery.voltage", [([], "int")]),
pytest.param("pybricks.hubs", "EssentialHub", "battery.current", [([], "int")]),
pytest.param("pybricks.hubs", "EssentialHub", "charger.connected", [([], "bool")]),
@@ -520,7 +577,12 @@ METHOD_PARAMS = [
"settings",
[(["max_voltage: Number"], "None"), ([], "Tuple[int]")],
),
pytest.param("pybricks.pupdevices", "Motor", "speed", [([], "int")]),
pytest.param(
"pybricks.pupdevices",
"Motor",
"speed",
[(["window: Number=100"], "int")],
),
pytest.param("pybricks.pupdevices", "Motor", "angle", [([], "int")]),
pytest.param(
"pybricks.pupdevices",
@@ -544,7 +606,7 @@ METHOD_PARAMS = [
"then: Stop=Stop.HOLD",
"wait: bool=True",
],
"None",
"MaybeAwaitable",
)
],
),
@@ -560,7 +622,7 @@ METHOD_PARAMS = [
"then: Stop=Stop.HOLD",
"wait: bool=True",
],
"None",
"MaybeAwaitable",
)
],
),
@@ -576,7 +638,7 @@ METHOD_PARAMS = [
"then: Stop=Stop.HOLD",
"wait: bool=True",
],
"None",
"MaybeAwaitable",
)
],
),
@@ -597,7 +659,7 @@ METHOD_PARAMS = [
"then: Stop=Stop.COAST",
"duty_limit: Optional[Number]=None",
],
"int",
"MaybeAwaitableInt",
)
],
),
@@ -637,12 +699,12 @@ METHOD_PARAMS = [
"kp: Optional[Number]=None",
"ki: Optional[Number]=None",
"kd: Optional[Number]=None",
"reserved: Optional[Number]=None",
"integral_deadzone: Optional[Number]=None",
"integral_rate: Optional[Number]=None",
],
"None",
),
([], "Tuple[int, int, int, None, int]"),
([], "Tuple[int, int, int, int, int]"),
],
),
pytest.param(
@@ -676,24 +738,53 @@ METHOD_PARAMS = [
],
),
pytest.param(
"pybricks.pupdevices", "TiltSensor", "tilt", [([], "Tuple[int, int]")]
),
pytest.param("pybricks.pupdevices", "InfraredSensor", "distance", [([], "int")]),
pytest.param("pybricks.pupdevices", "InfraredSensor", "reflection", [([], "int")]),
pytest.param("pybricks.pupdevices", "InfraredSensor", "count", [([], "int")]),
pytest.param(
"pybricks.pupdevices", "ColorDistanceSensor", "color", [([], "Color")]
"pybricks.pupdevices",
"TiltSensor",
"tilt",
[([], "MaybeAwaitableTuple[int, int]")],
),
pytest.param(
"pybricks.pupdevices", "ColorDistanceSensor", "reflection", [([], "int")]
"pybricks.pupdevices", "InfraredSensor", "distance", [([], "MaybeAwaitableInt")]
),
pytest.param(
"pybricks.pupdevices", "ColorDistanceSensor", "ambient", [([], "int")]
"pybricks.pupdevices",
"InfraredSensor",
"reflection",
[([], "MaybeAwaitableInt")],
),
pytest.param(
"pybricks.pupdevices", "ColorDistanceSensor", "distance", [([], "int")]
"pybricks.pupdevices", "InfraredSensor", "count", [([], "MaybeAwaitableInt")]
),
pytest.param(
"pybricks.pupdevices",
"ColorDistanceSensor",
"color",
[([], "MaybeAwaitableColor")],
),
pytest.param(
"pybricks.pupdevices",
"ColorDistanceSensor",
"reflection",
[([], "MaybeAwaitableInt")],
),
pytest.param(
"pybricks.pupdevices",
"ColorDistanceSensor",
"ambient",
[([], "MaybeAwaitableInt")],
),
pytest.param(
"pybricks.pupdevices",
"ColorDistanceSensor",
"distance",
[([], "MaybeAwaitableInt")],
),
pytest.param(
"pybricks.pupdevices",
"ColorDistanceSensor",
"hsv",
[([], "MaybeAwaitableColor")],
),
pytest.param("pybricks.pupdevices", "ColorDistanceSensor", "hsv", [([], "Color")]),
pytest.param(
"pybricks.pupdevices",
"ColorDistanceSensor",
@@ -704,27 +795,36 @@ METHOD_PARAMS = [
"pybricks.pupdevices",
"ColorDistanceSensor",
"light.on",
[(["color: Color"], "None")],
[(["color: Color"], "MaybeAwaitable")],
),
pytest.param(
"pybricks.pupdevices", "ColorDistanceSensor", "light.off", [([], "None")]
"pybricks.pupdevices",
"ColorDistanceSensor",
"light.off",
[([], "MaybeAwaitable")],
),
pytest.param("pybricks.pupdevices", "PFMotor", "dc", [(["duty: Number"], "None")]),
pytest.param("pybricks.pupdevices", "PFMotor", "stop", [([], "None")]),
pytest.param("pybricks.pupdevices", "PFMotor", "brake", [([], "None")]),
pytest.param(
"pybricks.pupdevices", "PFMotor", "dc", [(["duty: Number"], "MaybeAwaitable")]
),
pytest.param("pybricks.pupdevices", "PFMotor", "stop", [([], "MaybeAwaitable")]),
pytest.param("pybricks.pupdevices", "PFMotor", "brake", [([], "MaybeAwaitable")]),
pytest.param(
"pybricks.pupdevices",
"ColorSensor",
"color",
[(["surface: bool=True"], "Optional[Color]")],
[(["surface: bool=True"], "MaybeAwaitableColor")],
),
pytest.param(
"pybricks.pupdevices", "ColorSensor", "reflection", [([], "MaybeAwaitableInt")]
),
pytest.param(
"pybricks.pupdevices", "ColorSensor", "ambient", [([], "MaybeAwaitableInt")]
),
pytest.param("pybricks.pupdevices", "ColorSensor", "reflection", [([], "int")]),
pytest.param("pybricks.pupdevices", "ColorSensor", "ambient", [([], "int")]),
pytest.param(
"pybricks.pupdevices",
"ColorSensor",
"hsv",
[(["surface: bool=True"], "Color")],
[(["surface: bool=True"], "MaybeAwaitableColor")],
),
pytest.param(
"pybricks.pupdevices",
@@ -736,11 +836,28 @@ METHOD_PARAMS = [
"pybricks.pupdevices",
"ColorSensor",
"lights.on",
[(["brightness: Union[Number, Tuple[Number, Number, Number]]"], "None")],
[
(
["brightness: Union[Number, Tuple[Number, Number, Number]]"],
"MaybeAwaitable",
)
],
),
pytest.param(
"pybricks.pupdevices", "ColorSensor", "lights.off", [([], "MaybeAwaitable")]
),
pytest.param(
"pybricks.pupdevices",
"UltrasonicSensor",
"distance",
[([], "MaybeAwaitableInt")],
),
pytest.param(
"pybricks.pupdevices",
"UltrasonicSensor",
"presence",
[([], "MaybeAwaitableBool")],
),
pytest.param("pybricks.pupdevices", "ColorSensor", "lights.off", [([], "None")]),
pytest.param("pybricks.pupdevices", "UltrasonicSensor", "distance", [([], "int")]),
pytest.param("pybricks.pupdevices", "UltrasonicSensor", "presence", [([], "bool")]),
pytest.param(
"pybricks.pupdevices",
"UltrasonicSensor",
@@ -748,29 +865,40 @@ METHOD_PARAMS = [
[
(
["brightness: Union[Number, Tuple[Number, Number, Number, Number]]"],
"None",
"MaybeAwaitable",
)
],
),
pytest.param(
"pybricks.pupdevices", "UltrasonicSensor", "lights.off", [([], "None")]
"pybricks.pupdevices",
"UltrasonicSensor",
"lights.off",
[([], "MaybeAwaitable")],
),
pytest.param(
"pybricks.pupdevices", "ForceSensor", "force", [([], "MaybeAwaitableFloat")]
),
pytest.param(
"pybricks.pupdevices", "ForceSensor", "distance", [([], "MaybeAwaitableFloat")]
),
pytest.param("pybricks.pupdevices", "ForceSensor", "force", [([], "float")]),
pytest.param("pybricks.pupdevices", "ForceSensor", "distance", [([], "float")]),
pytest.param(
"pybricks.pupdevices",
"ForceSensor",
"pressed",
[(["force: Number=3"], "bool")],
[(["force: Number=3"], "MaybeAwaitableBool")],
),
pytest.param(
"pybricks.pupdevices", "ForceSensor", "touched", [([], "MaybeAwaitableBool")]
),
pytest.param("pybricks.pupdevices", "ForceSensor", "touched", [([], "bool")]),
pytest.param(
"pybricks.pupdevices",
"ColorLightMatrix",
"on",
[(["color: Union[Color, Collection[Color]]"], "None")],
[(["color: Union[Color, Collection[Color]]"], "MaybeAwaitable")],
),
pytest.param(
"pybricks.pupdevices", "ColorLightMatrix", "off", [([], "MaybeAwaitable")]
),
pytest.param("pybricks.pupdevices", "ColorLightMatrix", "off", [([], "None")]),
pytest.param(
"pybricks.pupdevices", "Light", "on", [(["brightness: Number=100"], "None")]
),
@@ -799,13 +927,23 @@ METHOD_PARAMS = [
"pybricks.robotics",
"DriveBase",
"straight",
[(["distance: Number", "then: Stop=Stop.HOLD", "wait: bool=True"], "None")],
[
(
["distance: Number", "then: Stop=Stop.HOLD", "wait: bool=True"],
"MaybeAwaitable",
)
],
),
pytest.param(
"pybricks.robotics",
"DriveBase",
"turn",
[(["angle: Number", "then: Stop=Stop.HOLD", "wait: bool=True"], "None")],
[
(
["angle: Number", "then: Stop=Stop.HOLD", "wait: bool=True"],
"MaybeAwaitable",
)
],
),
pytest.param(
"pybricks.robotics",
@@ -819,7 +957,7 @@ METHOD_PARAMS = [
"then: Stop=Stop.HOLD",
"wait: bool=True",
],
"None",
"MaybeAwaitable",
)
],
),
@@ -847,6 +985,7 @@ METHOD_PARAMS = [
[(["speed: Number", "turn_rate: Number"], "None")],
),
pytest.param("pybricks.robotics", "DriveBase", "stop", [([], "None")]),
pytest.param("pybricks.robotics", "DriveBase", "brake", [([], "None")]),
pytest.param("pybricks.robotics", "DriveBase", "distance", [([], "int")]),
pytest.param("pybricks.robotics", "DriveBase", "angle", [([], "int")]),
pytest.param(
+15
View File
@@ -2,6 +2,21 @@
<!-- refer to https://keepachangelog.com/en/1.0.0/ for guidance -->
## 2.10.0 - 2023-10-26
### Changed
- Updated docs to v3.3.0b9.
## 2.9.0 - 2023-05-16
### Changed
- Updated docs to v3.3.0b5.
## 2.8.0 - 2023-04-21
### Changed
- Updated docs to v3.3.0b4.
## 2.7.0 - 2022-12-20
### Changed
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2021 The Pybricks Authors
Copyright (c) 2018-2023 The Pybricks Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@pybricks/ide-docs",
"version": "2.7.0",
"version": "2.10.0",
"description": "Special build of Pybricks API docs for embedding in an IDE.",
"repository": {
"type": "git",
+15
View File
@@ -4,6 +4,21 @@
## Unreleased
## 1.9.0 - 2023-05-16
### Changed
- Updated `pybricks_jedi` Python package to v1.9.0.
## 1.8.0 - 2023-04-21
### Changed
- Updated `pybricks_jedi` Python package to v1.8.0.
## 1.7.0 - 2022-12-28
### Changed
- Updated `pybricks_jedi` Python package to v1.7.0.
## 1.6.0 - 2022-12-09
### Changed
+3 -3
View File
@@ -12,7 +12,7 @@ BUILD_DIR = (pathlib.Path(__file__).parent / "build").resolve()
package_json = {
"name": "@pybricks/jedi",
"version": "1.6.0",
"version": "1.9.0",
"description": "Binary distribution of pybricks-jedi Python package and dependencies for use with Pyodide.",
"repository": {
"type": "git",
@@ -30,7 +30,7 @@ whl_map: dict[str, str] = {}
shutil.rmtree(BUILD_DIR, True)
BUILD_DIR.mkdir()
# download package and depedencies (*.whl files)
# download package and dependencies (*.whl files)
subprocess.check_call(
[
sys.executable,
@@ -38,7 +38,7 @@ subprocess.check_call(
"pip",
"download",
"--only-binary=any",
"pybricks-jedi==1.6.0",
"pybricks-jedi==1.9.0",
],
cwd=BUILD_DIR,
)
Generated
+424 -336
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pybricks"
version = "3.2.0c2"
version = "3.3.0b9"
description = "Documentation and user-API stubs for Pybricks MicroPython"
authors = ["The Pybricks Authors <dev@pybricks.com>"]
maintainers = ["Laurens Valk <laurens@pybricks.com>", "David Lechner <david@pybricks.com>" ]
@@ -10,7 +10,6 @@ homepage = "https://pybricks.com"
repository = "https://github.com/pybricks/pybricks-api"
documentation = "https://docs.pybricks.com"
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: Implementation :: MicroPython",
]
packages = [
@@ -29,11 +28,13 @@ packages = [
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.dev-dependencies]
[tool.poetry.group.lint.dependencies]
black = "^22.3.0"
doc8 = "^0.8.1"
flake8 = "^4.0"
Sphinx = { git = "https://github.com/pybricks/sphinx.git", rev = "b00124cb" }
[tool.poetry.group.doc.dependencies]
Sphinx = { git = "https://github.com/pybricks/sphinx.git", rev = "cd277d09" }
sphinx-rtd-theme = "^1.0.0"
toml = "^0.10.0"
-5
View File
@@ -1,5 +0,0 @@
# This file is strictly for building docs on readthedocs.org
# See pyproject.toml for local development
git+https://github.com/pybricks/sphinx@b00124c#egg=Sphinx
sphinx-rtd-theme==1.0.0
toml
+1 -1
View File
@@ -1,6 +1,6 @@
[flake8]
exclude = .venv/,*.pyi,jedi/
exclude = .venv/,*.pyi,jedi/,examples/pup/tools/hub_menu.py
max-line-length = 88
ignore = E203,E501,W503
+329 -40
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2021 The Pybricks Authors
# Copyright (c) 2018-2023 The Pybricks Authors
"""Generic cross-platform module for typical devices like lights, displays,
speakers, and batteries."""
@@ -8,12 +8,35 @@ from __future__ import annotations
from typing import Union, Iterable, overload, Optional, Tuple, Collection, TYPE_CHECKING
from .geometry import Matrix, Axis
from .parameters import Direction, Stop, Button, Port, Color, Side
from .tools import Matrix
from .parameters import Axis, Direction, Stop, Button, Port, Color, Side
if TYPE_CHECKING:
from typing import Any, Awaitable, TypeVar
from .parameters import Number
_T_co = TypeVar("_T_co", covariant=True)
class MaybeAwaitable(None, Awaitable[None]):
...
# HACK: Cannot subclass bool, so using Any instead.
class MaybeAwaitableBool(Any, Awaitable[bool]):
...
class MaybeAwaitableFloat(float, Awaitable[float]):
...
class MaybeAwaitableInt(int, Awaitable[int]):
...
class MaybeAwaitableTuple(Tuple[_T_co], Awaitable[Tuple[_T_co]]):
...
class MaybeAwaitableColor(Color, Awaitable[Color]):
...
class System:
"""System control actions for a hub."""
@@ -78,8 +101,8 @@ class System:
def storage(self, offset, read=None, write=None):
"""
storage(self, offset, write=)
storage(self, offset, read=) -> bytes
storage(offset, write=)
storage(offset, read=) -> bytes
Reads or writes binary data to persistent storage.
@@ -219,18 +242,18 @@ class Control:
kp: Optional[Number] = None,
ki: Optional[Number] = None,
kd: Optional[Number] = None,
reserved: Optional[Number] = None,
integral_deadzone: Optional[Number] = None,
integral_rate: Optional[Number] = None,
) -> None:
...
@overload
def pid(self) -> Tuple[int, int, int, None, int]:
def pid(self) -> Tuple[int, int, int, int, int]:
...
def pid(self, *args):
"""pid(kp, ki, kd, reserved, integral_rate)
pid() -> Tuple[int, int, int, None, int]
"""pid(kp, ki, kd, integral_deadzone, integral_rate)
pid() -> Tuple[int, int, int, int, int]
Gets or sets the PID values for position and speed control.
@@ -245,7 +268,8 @@ class Control:
kd (int): Derivative position (or proportional speed) control
constant. It is the feedback torque per
unit of speed: µNm/(deg/s).
reserved: This setting is not used.
integral_deadzone (Number, deg or Number, mm): Zone around the
target where the error integral does not accumulate errors.
integral_rate (Number, deg/s or Number, mm/s): Maximum rate at
which the error integral is allowed to grow.
"""
@@ -303,6 +327,52 @@ class Control:
"""
class Model:
"""Class to interact with motor state observer and settings."""
def state(self) -> Tuple[float, float, float, bool]:
"""state() -> Tuple[float, float, float, bool]
Gets the estimated angle, speed, current, and stall state of the motor,
using a simulation model that mimics the real motor.
These estimates are updated faster than the real measurements,
which can be useful when building your own PID controllers.
For most applications it is better to used the *measured*
:meth:`angle <pybricks.pupdevices.Motor.angle>`,
:meth:`speed <pybricks.pupdevices.Motor.speed>`,
:meth:`load <pybricks.pupdevices.Motor.load>`, and
:meth:`stall <pybricks.pupdevices.Motor.stalled>` state instead.
Returns:
Tuple with the estimated angle (deg), speed (deg/s), current (mA),
and stall state (``True`` or ``False``).
"""
@overload
def settings(self, values: tuple) -> None:
...
@overload
def settings(self) -> tuple:
...
def settings(self, speed, time):
"""settings(values)
settings() -> Tuple
Gets or sets model settings as a tuple of integers. If no arguments are
given, this will return the current values. This method is mainly used
to debug the motor model class. Changing these settings should not be
needed in user programs.
.. _model settings: https://docs.pybricks.com/projects/pbio/en/latest/struct__pbio__observer__settings__t.html
Arguments:
values (Tuple): Tuple with `model settings`_.
"""
class Motor(DCMotor):
"""Generic class to control motors with built-in rotation sensors."""
@@ -312,14 +382,18 @@ class Motor(DCMotor):
``control`` attribute of the motor. See :ref:`control` for an overview
of available methods."""
model = Model()
"""Model representing the observer that estimates the motor state."""
def __init__(
self,
port: Port,
positive_direction: Direction = Direction.CLOCKWISE,
gears: Optional[Union[Collection[int], Collection[Collection[int]]]] = None,
reset_angle: bool = True,
profile: Number = None,
):
"""__init__(port, positive_direction=Direction.CLOCKWISE, gears=None, reset_angle=True)
"""__init__(port, positive_direction=Direction.CLOCKWISE, gears=None, reset_angle=True, profile=None)
Arguments:
port (Port): Port to which the motor is connected.
@@ -336,12 +410,18 @@ class Motor(DCMotor):
When you specify a gear train, all motor commands and settings
are automatically adjusted to account for the resulting gear
ratio. The motor direction remains unchanged by this.
reset_angle(bool):
reset_angle (bool):
Choose ``True`` to reset the rotation sensor value to the
absolute marker angle (between -180 and 179).
Choose ``False`` to keep the
current value, so your program knows where it left off last
time.
profile (Number, deg): Precision profile. This is the approximate
position tolerance in degrees that is acceptable in your
application. A lower value gives more precise but more erratic
movement; a higher value gives less precise but smoother
movement. If no value is given, a suitable profile for this
motor type will be selected automatically (about 11 degrees).
"""
def angle(self) -> int:
@@ -353,11 +433,19 @@ class Motor(DCMotor):
Motor angle.
"""
def speed(self) -> int:
"""speed() -> int: deg/s
def speed(self, window: Number = 100) -> int:
"""speed(window=100) -> int: deg/s
Gets the speed of the motor.
The speed is measured as the change in the motor angle during the
given time window. A short window makes the speed value more
responsive to motor movement, but less steady. A long window makes the
speed value less responsive, but more steady.
Arguments:
window (Number, ms): The time window used to determine the speed.
Returns:
Motor speed.
@@ -413,7 +501,7 @@ class Motor(DCMotor):
def run_time(
self, speed: Number, time: Number, then: Stop = Stop.HOLD, wait: bool = True
) -> None:
) -> MaybeAwaitable:
"""run_time(speed, time, then=Stop.HOLD, wait=True)
Runs the motor at a constant speed for a given amount of time.
@@ -436,7 +524,7 @@ class Motor(DCMotor):
rotation_angle: Number,
then: Stop = Stop.HOLD,
wait: bool = True,
) -> None:
) -> MaybeAwaitable:
"""run_angle(speed, rotation_angle, then=Stop.HOLD, wait=True)
Runs the motor at a constant speed by a given angle.
@@ -456,7 +544,7 @@ class Motor(DCMotor):
target_angle: Number,
then: Stop = Stop.HOLD,
wait: bool = True,
) -> None:
) -> MaybeAwaitable:
"""run_target(speed, target_angle, then=Stop.HOLD, wait=True)
Runs the motor at a constant speed towards a given target angle.
@@ -477,7 +565,7 @@ class Motor(DCMotor):
speed: Number,
then: Stop = Stop.COAST,
duty_limit: Optional[Number] = None,
) -> int:
) -> MaybeAwaitableInt:
"""
run_until_stalled(speed, then=Stop.COAST, duty_limit=None) -> int: deg
@@ -541,7 +629,7 @@ class Speaker:
volume (Number, %): Volume of the speaker in the 0-100 range.
"""
def beep(self, frequency: Number = 500, duration: Number = 100) -> None:
def beep(self, frequency: Number = 500, duration: Number = 100) -> MaybeAwaitable:
"""beep(frequency=500, duration=100)
Play a beep/tone.
@@ -555,7 +643,7 @@ class Speaker:
play continues to play indefinitely.
"""
def play_notes(self, notes: Iterable[str], tempo: Number = 120) -> None:
def play_notes(self, notes: Iterable[str], tempo: Number = 120) -> MaybeAwaitable:
"""play_notes(notes, tempo=120)
Plays a sequence of musical notes. For example:
@@ -642,10 +730,31 @@ class ColorLight:
"""
class ExternalColorLight:
"""Control a multi-color light."""
def on(self, color: Color) -> MaybeAwaitable:
"""on(color)
Turns on the light at the specified color.
Arguments:
color (Color): Color of the light.
"""
def off(self) -> MaybeAwaitable:
"""off()
Turns off the light.
"""
class LightArray3:
"""Control an array of three single-color lights."""
def on(self, brightness: Union[Number, Tuple[Number, Number, Number]]) -> None:
def on(
self, brightness: Union[Number, Tuple[Number, Number, Number]]
) -> MaybeAwaitable:
"""on(brightness)
Turns on the lights at the specified brightness.
@@ -657,10 +766,11 @@ class LightArray3:
of each light individually.
"""
def off(self) -> None:
def off(self) -> MaybeAwaitable:
"""off()
Turns off all the lights."""
Turns off all the lights.
"""
class LightArray4(LightArray3):
@@ -668,7 +778,7 @@ class LightArray4(LightArray3):
def on(
self, brightness: Union[Number, Tuple[Number, Number, Number, Number]]
) -> None:
) -> MaybeAwaitable:
"""on(brightness)
Turns on the lights at the specified brightness.
@@ -731,7 +841,7 @@ class LightMatrix:
Arguments:
matrices (iter): Sequence of
:class:`Matrix <pybricks.geometry.Matrix>` of intensities.
:class:`Matrix <pybricks.tools.Matrix>` of intensities.
interval (Number, ms): Time to display each image in the list.
"""
@@ -928,22 +1038,94 @@ class Accelerometer(SimpleAccelerometer):
along the x-axis.
Returns:
Tuple of pitch and roll angles.
Tuple of pitch and roll angles in degrees.
"""
class IMU(Accelerometer):
def ready(self) -> bool:
"""ready() -> bool
Checks if the device is calibrated and ready for use.
This becomes ``True`` when the robot has been sitting stationary for a
few seconds, which allows the device to re-calibrate. It is ``False``
if the hub has just been started, or if it hasn't had a chance to
calibrate for more than 10 minutes.
Returns:
``True`` if it is ready for use, ``False`` if not.
"""
def stationary(self) -> bool:
"""stationary() -> bool
Checks if the device is currently stationary (not moving).
Returns:
``True`` if stationary for at least a second, ``False`` if it is
moving.
"""
@overload
def settings(
self,
angular_velocity_threshold: float = None,
acceleration_threshold: float = None,
) -> None:
...
@overload
def settings(self) -> Tuple[float, float]:
...
def settings(self, *args):
"""
settings(angular_velocity_threshold, acceleration_threshold)
settings() -> Tuple[float, float]
Configures the IMU settings. If no arguments are given,
this returns the current values.
The ``angular_velocity_threshold`` and ``acceleration_threshold``
define when the hub is considered stationary. If all
measurements stay below these thresholds for one second, the IMU
will recalibrate itself.
In a noisy room with high ambient vibrations (such as a
competition hall), it is recommended to increase the thresholds
slightly to give your robot the chance to calibrate.
To verify that your settings are working as expected, test that
the ``stationary()`` method gives ``False`` if your robot is moving,
and ``True`` if it is sitting still for at least a second.
Arguments:
angular_velocity_threshold (Number, deg/s): The threshold for
angular velocity. The default value is 1.5 deg/s.
acceleration_threshold (Number, mm/): The threshold for angular
velocity. The default value is 250 mm/.
"""
def heading(self) -> float:
"""heading() -> float: deg
Gets the heading angle relative to the starting orientation. It is a
positive rotation around the :ref:`z-axis in the robot
frame <robotframe>`, prior to applying any tilt rotation.
Gets the heading angle of your robot. A positive value means a
clockwise turn.
For a vehicle viewed from the top, this means that
a positive heading value corresponds to a counterclockwise rotation.
The heading is 0 when your program starts. The value continues to grow
even as the robot turns more than 180 degrees. It does not wrap around
to -180 like it does in some apps.
.. note:: This method is not yet implemented.
.. note:: *For now, this method only keeps track of the heading while
the robot is on a flat surface.*
This means that the value is
no longer correct if you lift it from the table. To solve
this, you can call ``reset_heading`` to reset the heading to
a known value *after* you put it back down. For example, you
could align your robot with the side of the competition table
and reset the heading 90 degrees as the new starting point.
Returns:
Heading angle relative to starting orientation.
@@ -955,8 +1137,6 @@ class IMU(Accelerometer):
Resets the accumulated heading angle of the robot.
.. note:: This method is not yet implemented.
Arguments:
angle (Number, deg): Value to which the heading should be reset.
"""
@@ -985,6 +1165,41 @@ class IMU(Accelerometer):
this returns a vector of accelerations along all axes.
"""
def rotation(self, axis: Axis) -> float:
"""
rotation(axis) -> float: deg
Gets the rotation of the device along a given axis in
the :ref:`robot reference frame <robotframe>`.
This value is useful if your robot *only* rotates along the requested
axis. For general three-dimensional motion, use the
``orientation()`` method instead.
The value starts counting from ``0`` when you initialize this class.
Arguments:
axis (Axis): Axis along which the rotation should be measured.
Returns:
The rotation angle.
"""
def orientation(self) -> Matrix:
"""
orientation() -> Matrix
Gets the three-dimensional orientation of the robot in
the :ref:`robot reference frame <robotframe>`.
It returns a rotation matrix whose columns represent the ``X``, ``Y``,
and ``Z`` axis of the robot.
.. note:: This method is not yet implemented.
Returns:
The rotation matrix.
"""
class CommonColorSensor:
"""Generic color sensor that supports Pybricks color calibration."""
@@ -996,7 +1211,7 @@ class CommonColorSensor:
port (Port): Port to which the sensor is connected.
"""
def color(self) -> Color:
def color(self) -> MaybeAwaitableColor:
"""color() -> Color
Scans the color of a surface.
@@ -1010,7 +1225,7 @@ class CommonColorSensor:
Detected color.
"""
def hsv(self) -> Color:
def hsv(self) -> MaybeAwaitableColor:
"""hsv() -> Color
Scans the color of a surface.
@@ -1024,7 +1239,7 @@ class CommonColorSensor:
saturation (0--100), and a brightness value (0--100).
"""
def ambient(self) -> int:
def ambient(self) -> MaybeAwaitableInt:
"""ambient() -> int: %
Measures the ambient light intensity.
@@ -1034,7 +1249,7 @@ class CommonColorSensor:
to 100% (bright).
"""
def reflection(self) -> int:
def reflection(self) -> MaybeAwaitableInt:
"""reflection() -> int: %
Measures how much a surface reflects the light emitted by the
@@ -1080,7 +1295,7 @@ class AmbientColorSensor(CommonColorSensor):
"""Like CommonColorSensor, but also detects ambient colors when the sensor
light is turned off"""
def color(self, surface: bool = True) -> Optional[Color]:
def color(self, surface: bool = True) -> MaybeAwaitableColor:
"""color(surface=True) -> Color
Scans the color of a surface or an external light source.
@@ -1099,7 +1314,7 @@ class AmbientColorSensor(CommonColorSensor):
Detected color.`
"""
def hsv(self, surface: bool = True) -> Color:
def hsv(self, surface: bool = True) -> MaybeAwaitableColor:
"""hsv(surface=True) -> Color
Scans the color of a surface or an external light source.
@@ -1117,3 +1332,77 @@ class AmbientColorSensor(CommonColorSensor):
Measured color. The color is described by a hue (0--359), a
saturation (0--100), and a brightness value (0--100).
"""
class BLE:
"""
Bluetooth Low Energy.
.. versionadded:: 3.3
"""
def broadcast(self, data: Union[bool, int, float, str, bytes]) -> None:
"""broadcast(data)
Starts broadcasting the given data on
the *broadcast_channel* you selected when initializing the hub.
Data may be of type ``int``, ``float``, ``str``, ``bytes``,
``True``, or ``False``, or a tuple thereof.
The total data size is quite limited (26 bytes). ``True`` and
``False`` take 1 byte each. ``float`` takes 5 bytes. ``int`` takes 2 to
5 bytes depending on how big the number is. ``str`` and ``bytes`` take
the number of bytes in the object plus one extra byte.
Args:
data: The value or values to be broadcast.
.. versionadded:: 3.3
"""
def observe(
self, channel: int
) -> Optional[Tuple[Union[bool, int, float, str, bytes], ...]]:
"""observe(channel) -> bool | int | float | str | bytes | tuple | None
Retrieves the last observed data for a given channel.
Receiving data is more reliable when the hub is not connected
to a computer or other devices at the same time.
Args:
channel (int): The channel to observe (0 to 255).
Returns:
The received data in the same format as it was sent, or ``None``
if no recent data is available.
.. versionadded:: 3.3
"""
def signal_strength(self, channel: int) -> int:
"""signal_strength(channel) -> int: dBm
Gets the average signal strength in dBm for the given channel.
This indicates how near the broadcasting device is. Nearby devices
may have a signal strength around -40 dBm, while far away devices
might have a signal strength around -70 dBm.
Args:
channel (int): The channel number (0 to 255).
Returns:
The signal strength or ``-128`` if there is no recent observed data.
.. versionadded:: 3.3
"""
def version(self) -> str:
"""version() -> str
Gets the firmware version from the Bluetooth chip.
.. versionadded:: 3.3
"""
+2 -4
View File
@@ -168,14 +168,12 @@ class InfraredSensor:
class GyroSensor:
"""LEGO® MINDSTORMS® EV3 Gyro Sensor."""
def __init__(
self, port: _Port, positive_direction: _Direction = _Direction.CLOCKWISE
):
def __init__(self, port: _Port, direction: _Direction = _Direction.CLOCKWISE):
"""GyroSensor(port)
Arguments:
port (Port): Port to which the sensor is connected.
positive_direction (Direction):
direction (Direction):
Positive rotation direction when looking at the red dot on top
of the sensor.
-132
View File
@@ -1,132 +0,0 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2022 The Pybricks Authors
"""Core linear algebra functionality for orientation sensors and robotics."""
from __future__ import annotations
from typing import Sequence, Tuple, overload
class Matrix:
"""Mathematical representation of a matrix. It supports
addition (``A + B``), subtraction (``A - B``),
and matrix multiplication (``A * B``) for matrices of compatible size.
It also supports scalar multiplication (``c * A`` or ``A * c``)
and scalar division (``A / c``).
A :class:`.Matrix` object is immutable."""
def __add__(self, other) -> Matrix:
...
def __iadd__(self, other) -> Matrix:
...
def __sub__(self, other) -> Matrix:
...
def __isub__(self, other) -> Matrix:
...
def __mul__(self, other) -> Matrix:
...
def __rmul__(self, other) -> Matrix:
...
def __imul__(self, other) -> Matrix:
...
def __truediv__(self, other) -> Matrix:
...
def __itruediv__(self, other) -> Matrix:
...
def __floordiv__(self, other) -> Matrix:
...
def __ifloordiv__(self, other) -> Matrix:
...
def __init__(self, rows: Sequence[Sequence[float]]):
"""Matrix(rows)
Arguments:
rows (list): List of rows. Each row is itself a list of numbers.
"""
@property
def T(self) -> Matrix: # noqa: N802
"""Returns a new :class:`.Matrix` that is the transpose of the
original."""
@property
def shape(self) -> Tuple[int, int]:
"""Returns a tuple (``m``, ``n``),
where ``m`` is the number of rows and ``n`` is the number of columns.
"""
@overload
def vector(x: float, y: float) -> Matrix:
"""
Convenience function to create a :class:`.Matrix` with the shape (``2``, ``1``).
Arguments:
x (float): x-coordinate of the vector.
y (float): y-coordinate of the vector.
Returns:
A matrix with the shape of a column vector.
"""
@overload
def vector(x: float, y: float, z: float) -> Matrix:
"""
Convenience function to create a :class:`.Matrix` with the shape (``3``, ``1``).
Arguments:
x (float): x-coordinate of the vector.
y (float): y-coordinate of the vector.
z (float): z-coordinate of the vector.
Returns:
A matrix with the shape of a column vector.
"""
def vector(*args):
"""
vector(x, y) -> Matrix
vector(x, y, z) -> Matrix
Convenience function to create a :class:`.Matrix` with the
shape (``2``, ``1``) or (``3``, ``1``).
Arguments:
x (float): x-coordinate of the vector.
y (float): y-coordinate of the vector.
z (float): z-coordinate of the vector (optional).
Returns:
A matrix with the shape of a column vector.
"""
class Axis:
"""Unit axes of a coordinate system.
.. data:: X = vector(1, 0, 0)
.. data:: Y = vector(0, 1, 0)
.. data:: Z = vector(0, 0, 1)
"""
X: Matrix = vector(1, 0, 0)
Y: Matrix = vector(0, 1, 0)
Z: Matrix = vector(0, 0, 1)
+100 -9
View File
@@ -1,12 +1,14 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2022 The Pybricks Authors
# Copyright (c) 2018-2023 The Pybricks Authors
"""LEGO® Programmable Hubs."""
from typing import Sequence
from . import _common
from .ev3dev import _speaker
from .geometry import Axis
from .media.ev3dev import Image as _Image
from .parameters import Button as _Button
from .parameters import Button as _Button, Axis
class EV3Brick:
@@ -39,6 +41,25 @@ class MoveHub:
imu = _common.SimpleAccelerometer()
system = _common.System()
button = _common.Keypad([_Button.CENTER])
ble = _common.BLE()
def __init__(
self, broadcast_channel: int = 0, observe_channels: Sequence[int] = []
):
"""MoveHub(broadcast_channel=0, observe_channels=[])
Arguments:
broadcast_channel:
A value from 0 to 255 indicating which channel ``hub.ble.broadcast()``
will use. Default is channel 0.
observe_channels:
A list of channels to listen to when ``hub.ble.observe()`` is
called. Listening to more channels requires more memory.
Default is an empty list (no channels).
.. versionchanged:: 3.3
Added *broadcast_channel* and *observe_channels* arguments.
"""
class CityHub:
@@ -50,6 +71,25 @@ class CityHub:
light = _common.ColorLight()
system = _common.System()
button = _common.Keypad([_Button.CENTER])
ble = _common.BLE()
def __init__(
self, broadcast_channel: int = 0, observe_channels: Sequence[int] = []
):
"""CityHub(broadcast_channel=0, observe_channels=[])
Arguments:
broadcast_channel:
A value from 0 to 255 indicating which channel ``hub.ble.broadcast()``
will use. Default is channel 0.
observe_channels:
A list of channels to listen to when ``hub.ble.observe()`` is
called. Listening to more channels requires more memory.
Default is an empty list (no channels).
.. versionchanged:: 3.3
Added *broadcast_channel* and *observe_channels* arguments.
"""
class TechnicHub:
@@ -62,9 +102,16 @@ class TechnicHub:
imu = _common.IMU()
system = _common.System()
button = _common.Keypad([_Button.CENTER])
ble = _common.BLE()
def __init__(self, top_side: Axis = Axis.Z, front_side: Axis = Axis.X):
"""TechnicHub(top_side=Axis.Z, front_side=Axis.X)
def __init__(
self,
top_side: Axis = Axis.Z,
front_side: Axis = Axis.X,
broadcast_channel: int = 0,
observe_channels: Sequence[int] = [],
):
"""TechnicHub(top_side=Axis.Z, front_side=Axis.X, broadcast_channel=0, observe_channels=[])
Initializes the hub. Optionally, specify how the hub is
:ref:`placed in your design <robotframe>` by saying in which
@@ -76,6 +123,16 @@ class TechnicHub:
the hub.
front_side (Axis): The axis that passes through the *front side* of
the hub.
broadcast_channel:
A value from 0 to 255 indicating which channel ``hub.ble.broadcast()``
will use. Default is channel 0.
observe_channels:
A list of channels to listen to when ``hub.ble.observe()`` is
called. Listening to more channels requires more memory.
Default is an empty list (no channels).
.. versionchanged:: 3.3
Added *broadcast_channel* and *observe_channels* arguments.
"""
@@ -90,9 +147,16 @@ class EssentialHub:
light = _common.ColorLight()
imu = _common.IMU()
system = _common.System()
ble = _common.BLE()
def __init__(self, top_side=Axis.Z, front_side=Axis.X):
"""__init__(top_side=Axis.Z, front_side=Axis.X)
def __init__(
self,
top_side: Axis = Axis.Z,
front_side: Axis = Axis.X,
broadcast_channel: int = 0,
observe_channels: Sequence[int] = [],
):
"""EssentialHub(top_side=Axis.Z, front_side=Axis.X, broadcast_channel=0, observe_channels=[])
Initializes the hub. Optionally, specify how the hub is
:ref:`placed in your design <robotframe>` by saying in which
@@ -104,6 +168,16 @@ class EssentialHub:
the hub.
front_side (Axis): The axis that passes through the *front side* of
the hub.
broadcast_channel:
A value from 0 to 255 indicating which channel ``hub.ble.broadcast()``
will use. Default is channel 0.
observe_channels:
A list of channels to listen to when ``hub.ble.observe()`` is
called. Listening to more channels requires more memory.
Default is an empty list (no channels).
.. versionchanged:: 3.3
Added *broadcast_channel* and *observe_channels* arguments.
"""
pass
@@ -128,9 +202,16 @@ class PrimeHub:
speaker = _common.Speaker()
imu = _common.IMU()
system = _common.System()
ble = _common.BLE()
def __init__(self, top_side: Axis = Axis.Z, front_side: Axis = Axis.X):
"""PrimeHub(top_side=Axis.Z, front_side=Axis.X)
def __init__(
self,
top_side: Axis = Axis.Z,
front_side: Axis = Axis.X,
broadcast_channel: int = 0,
observe_channels: Sequence[int] = [],
):
"""PrimeHub(top_side=Axis.Z, front_side=Axis.X, broadcast_channel=0, observe_channels=[])
Initializes the hub. Optionally, specify how the hub is
:ref:`placed in your design <robotframe>` by saying in which
@@ -142,6 +223,16 @@ class PrimeHub:
the hub.
front_side (Axis): The axis that passes through the *front side* of
the hub.
broadcast_channel:
A value from 0 to 255 indicating which channel ``hub.ble.broadcast()``
will use. Default is channel 0.
observe_channels:
A list of channels to listen to when ``hub.ble.observe()`` is
called. Listening to more channels requires more memory.
Default is an empty list (no channels).
.. versionchanged:: 3.3
Added *broadcast_channel* and *observe_channels* arguments.
"""
+17 -6
View File
@@ -1,13 +1,18 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2020 The Pybricks Authors
# Copyright (c) 2018-2023 The Pybricks Authors
"""Generic input/output devices."""
from typing import Dict, Tuple, Optional, overload
from __future__ import annotations
from typing import Dict, Tuple, Optional, overload, TYPE_CHECKING
from . import _common
from .parameters import Port as _Port
if TYPE_CHECKING:
from ._common import MaybeAwaitable, MaybeAwaitableTuple
class PUPDevice:
"""Powered Up motor or sensor."""
@@ -28,7 +33,7 @@ class PUPDevice:
Dictionary with information, such as the device ``id``.
"""
def read(self, mode: int) -> Tuple:
def read(self, mode: int) -> MaybeAwaitableTuple:
"""read(mode) -> Tuple
Reads values from a given mode.
@@ -40,7 +45,7 @@ class PUPDevice:
Values read from the sensor.
"""
def write(self, mode: int, data: Tuple) -> None:
def write(self, mode: int, data: Tuple) -> MaybeAwaitable:
"""write(mode, data)
Writes values to the sensor. Only selected sensors and modes support
@@ -62,7 +67,7 @@ class LUMPDevice:
port (Port): Port to which the device is connected.
"""
def read(self, mode: int) -> Tuple:
def read(self, mode: int) -> MaybeAwaitableTuple:
"""read(mode) -> Tuple
Reads values from a given mode.
@@ -95,7 +100,7 @@ class Ev3devSensor:
port (Port): Port to which the device is connected.
"""
def read(self, mode: str) -> Tuple:
def read(self, mode: str) -> MaybeAwaitableTuple:
"""read(mode) -> Tuple
Reads values at a given mode.
@@ -331,3 +336,9 @@ class LWP3Device:
Returns:
The raw binary message.
"""
# hide from jedi
if TYPE_CHECKING:
del MaybeAwaitable
del MaybeAwaitableTuple
+15 -1
View File
@@ -9,7 +9,7 @@ from enum import Enum
from typing import Union, TYPE_CHECKING
import os
from .geometry import Matrix as _Matrix
from .tools import Matrix as _Matrix, vector as _vector
if TYPE_CHECKING or os.environ.get("SPHINX_BUILD") == "True":
Number = Union[int, float]
@@ -57,6 +57,20 @@ class _PybricksEnum(Enum, metaclass=_PybricksEnumMeta):
return str(self)
class Axis:
"""Unit axes of a coordinate system.
.. data:: X = vector(1, 0, 0)
.. data:: Y = vector(0, 1, 0)
.. data:: Z = vector(0, 0, 1)
"""
X: _Matrix = _vector(1, 0, 0)
Y: _Matrix = _vector(0, 1, 0)
Z: _Matrix = _vector(0, 0, 1)
class Color:
"""Light or surface color."""
+65 -20
View File
@@ -1,16 +1,23 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2022 The Pybricks Authors
# Copyright (c) 2018-2023 The Pybricks Authors
"""LEGO® Powered Up motor, sensors, and lights."""
from __future__ import annotations
from typing import TYPE_CHECKING, Collection, Optional, Union, overload, Tuple
from typing import TYPE_CHECKING, Collection, Optional, Union, overload
from . import _common
from .parameters import Button, Color, Direction
if TYPE_CHECKING:
from ._common import (
MaybeAwaitable,
MaybeAwaitableBool,
MaybeAwaitableFloat,
MaybeAwaitableInt,
MaybeAwaitableTuple,
)
from .parameters import Number, Port
@@ -38,8 +45,9 @@ class Motor(_common.Motor):
positive_direction: Direction = Direction.CLOCKWISE,
gears: Optional[Union[Collection[int], Collection[Collection[int]]]] = None,
reset_angle: bool = True,
profile: Number = None,
):
"""__init__(port, positive_direction=Direction.CLOCKWISE, gears=None, reset_angle=True)
"""__init__(port, positive_direction=Direction.CLOCKWISE, gears=None, reset_angle=True, profile=None)
Arguments:
port (Port): Port to which the motor is connected.
@@ -56,12 +64,18 @@ class Motor(_common.Motor):
When you specify a gear train, all motor commands and settings
are automatically adjusted to account for the resulting gear
ratio. The motor direction remains unchanged by this.
reset_angle(bool):
reset_angle (bool):
Choose ``True`` to reset the rotation sensor value to the
absolute marker angle (between -180 and 179).
Choose ``False`` to keep the
current value, so your program knows where it left off last
time.
profile (Number, deg): Precision profile. This is the approximate
position tolerance in degrees that is acceptable in your
application. A lower value gives more precise but more erratic
movement; a higher value gives less precise but smoother
movement. If no value is given, a suitable profile for this
motor type will be selected automatically (about 11 degrees).
"""
def reset_angle(self, angle: Optional[Number] = None) -> None:
@@ -92,7 +106,7 @@ class Remote:
Button.RIGHT_PLUS,
)
)
addresss: Union[str, None]
address: Union[str, None]
def __init__(self, name: Optional[str] = None, timeout: int = 10000):
"""Remote(name=None, timeout=10000)
@@ -139,7 +153,7 @@ class TiltSensor:
port (Port): Port to which the sensor is connected.
"""
def tilt(self) -> Tuple[int, int]:
def tilt(self) -> MaybeAwaitableTuple[int, int]:
"""tilt() -> Tuple[int, int]: deg
Measures the tilt relative to the horizontal plane.
@@ -152,7 +166,7 @@ class TiltSensor:
class ColorDistanceSensor(_common.CommonColorSensor):
"""LEGO® Powered Up Color and Distance Sensor."""
light = _common.ColorLight()
light = _common.ExternalColorLight()
# HACK: jedi can't find inherited __init__ so docs have to be duplicated
def __init__(self, port: Port):
@@ -162,7 +176,7 @@ class ColorDistanceSensor(_common.CommonColorSensor):
port (Port): Port to which the sensor is connected.
"""
def distance(self) -> int:
def distance(self) -> MaybeAwaitableInt:
"""distance() -> int: %
Measures the relative distance between the sensor and an object
@@ -173,7 +187,7 @@ class ColorDistanceSensor(_common.CommonColorSensor):
"""
class PFMotor(DCMotor):
class PFMotor:
"""Control Power Functions motors with the infrared functionality of the
:class:`ColorDistanceSensor <pybricks.pupdevices.ColorDistanceSensor>`."""
@@ -199,6 +213,32 @@ class PFMotor(DCMotor):
turn when you give a positive duty cycle value.
"""
def dc(self, duty: Number) -> MaybeAwaitable:
"""dc(duty)
Rotates the motor at a given duty cycle (also known as "power").
Arguments:
duty (Number, %): The duty cycle (-100.0 to 100).
"""
def stop(self) -> MaybeAwaitable:
"""stop()
Stops the motor and lets it spin freely.
The motor gradually stops due to friction.
"""
def brake(self) -> MaybeAwaitable:
"""brake()
Passively brakes the motor.
The motor stops due to friction, plus the voltage that
is generated while the motor is still moving.
"""
class ColorSensor(_common.AmbientColorSensor):
"""LEGO® SPIKE Color Sensor."""
@@ -227,7 +267,7 @@ class UltrasonicSensor:
"""
def distance(self) -> int:
def distance(self) -> MaybeAwaitableInt:
"""distance() -> int: mm
Measures the distance between the sensor and an object using
@@ -239,7 +279,7 @@ class UltrasonicSensor:
"""
def presence(self) -> bool:
def presence(self) -> MaybeAwaitableBool:
"""presence() -> bool
Checks for the presence of other ultrasonic sensors by detecting
@@ -260,7 +300,7 @@ class ForceSensor:
port (Port): Port to which the sensor is connected.
"""
def force(self) -> float:
def force(self) -> MaybeAwaitableFloat:
"""force() -> float: N
Measures the force exerted on the sensor.
@@ -269,7 +309,7 @@ class ForceSensor:
Measured force (up to approximately 10.00 N).
"""
def distance(self) -> float:
def distance(self) -> MaybeAwaitableFloat:
"""distance() -> float: mm
Measures by how much the sensor button has moved.
@@ -278,7 +318,7 @@ class ForceSensor:
Movement up to approximately 8.00 mm.
"""
def pressed(self, force: Number = 3) -> bool:
def pressed(self, force: Number = 3) -> MaybeAwaitableBool:
"""pressed(force=3) -> bool
Checks if the sensor button is pressed.
@@ -290,7 +330,7 @@ class ForceSensor:
``True`` if the sensor is pressed, ``False`` if it is not.
"""
def touched(self) -> bool:
def touched(self) -> MaybeAwaitableBool:
"""touched() -> bool
Checks if the sensor is touched.
@@ -318,7 +358,7 @@ class ColorLightMatrix:
"""
...
def on(self, color: Union[Color, Collection[Color]]) -> None:
def on(self, color: Union[Color, Collection[Color]]) -> MaybeAwaitable:
"""on(colors)
Turns the lights on.
@@ -331,7 +371,7 @@ class ColorLightMatrix:
"""
...
def off(self) -> None:
def off(self) -> MaybeAwaitable:
"""off()
Turns all lights off.
@@ -349,7 +389,7 @@ class InfraredSensor:
port (Port): Port to which the sensor is connected.
"""
def reflection(self) -> int:
def reflection(self) -> MaybeAwaitableInt:
"""reflection() -> int: %
Measures the reflection of a surface using an infrared light.
@@ -359,7 +399,7 @@ class InfraredSensor:
100% (high reflection).
"""
def distance(self) -> int:
def distance(self) -> MaybeAwaitableInt:
"""distance() -> int: %
Measures the relative distance between the sensor and an object
@@ -369,7 +409,7 @@ class InfraredSensor:
Distance ranging from 0% (closest) to 100% (farthest).
"""
def count(self) -> int:
def count(self) -> MaybeAwaitableInt:
"""count() -> int
Counts the number of objects that have passed by the sensor.
@@ -410,5 +450,10 @@ if TYPE_CHECKING:
del Button
del Color
del Direction
del MaybeAwaitable
del MaybeAwaitableBool
del MaybeAwaitableFloat
del MaybeAwaitableInt
del MaybeAwaitableTuple
del Number
del Port
+37 -14
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2022 The Pybricks Authors
# Copyright (c) 2018-2023 The Pybricks Authors
"""Robotics module for the Pybricks API."""
@@ -11,7 +11,7 @@ from . import _common
from .parameters import Stop
if TYPE_CHECKING:
from ._common import Motor
from ._common import Motor, MaybeAwaitable
from .parameters import Number
@@ -28,9 +28,7 @@ class DriveBase:
**Positive** angles and turn rates mean turning **right**.
**Negative** means **left**. So when viewed from the top,
positive means clockwise and negative means counterclockwise. If desired,
you can flip this convention by reversing the ``left_motor`` and
``right_motor`` when you initialize this class.
positive means clockwise and negative means counterclockwise.
See the `measuring`_ section for tips to measure and adjust the diameter
and axle track values.
@@ -88,6 +86,12 @@ class DriveBase:
Stops the robot by letting the motors spin freely."""
def brake(self) -> None:
"""brake()
Stops the robot by passively braking the motors.
"""
def distance(self) -> int:
"""distance() -> int: mm
@@ -135,30 +139,35 @@ class DriveBase:
...
def settings(self, *args):
"""settings(straight_speed, straight_acceleration, turn_rate, turn_acceleration)
"""
settings(straight_speed, straight_acceleration, turn_rate, turn_acceleration)
settings() -> Tuple[int, int, int, int]
Configures the speed and acceleration used
by :meth:`.straight`, :meth:`.turn`, and :meth:`.curve`.
Configures the drive base speed and acceleration.
If you give no arguments, this returns the current values as a tuple.
The default values are automatically configured based on your wheel
The initial values are automatically configured based on your wheel
diameter and axle track. They are selected such that your robot
drives at about 40% of its maximum speed.
The speed values given here do not apply to the :meth:`.drive` method,
since you provide your own speed values as arguments in that method.
Arguments:
straight_speed (Number, mm/s): Straight-line speed of the robot.
straight_acceleration (Number, mm/): Straight-line
acceleration and deceleration of the robot.
acceleration and deceleration of the robot. Provide a tuple with
two values to set acceleration and deceleration separately.
turn_rate (Number, deg/s): Turn rate of the robot.
turn_acceleration (Number, deg/): Angular acceleration and
deceleration of the robot.
deceleration of the robot. Provide a tuple with
two values to set acceleration and deceleration separately.
"""
def straight(
self, distance: Number, then: Stop = Stop.HOLD, wait: bool = True
) -> None:
) -> MaybeAwaitable:
"""straight(distance, then=Stop.HOLD, wait=True)
Drives straight for a given distance and then stops.
@@ -170,7 +179,9 @@ class DriveBase:
with the rest of the program.
"""
def turn(self, angle: Number, then: Stop = Stop.HOLD, wait: bool = True) -> None:
def turn(
self, angle: Number, then: Stop = Stop.HOLD, wait: bool = True
) -> MaybeAwaitable:
"""turn(angle, then=Stop.HOLD, wait=True)
Turns in place by a given angle and then stops.
@@ -184,7 +195,7 @@ class DriveBase:
def curve(
self, radius: Number, angle: Number, then: Stop = Stop.HOLD, wait: bool = True
) -> None:
) -> MaybeAwaitable:
"""curve(radius, angle, then=Stop.HOLD, wait=True)
Drives an arc along a circle of a given radius, by a given angle.
@@ -218,9 +229,21 @@ class DriveBase:
``True`` if the drivebase is stalled, ``False`` if not.
"""
def use_gyro(self, use_gyro: bool) -> None:
"""use_gyro(use_gyro)
Choose ``True`` to use the gyro sensor for turning and driving
straight. Choose ``False`` to rely only on the motor's built-in
rotation sensors.
Arguments:
use_gyro (bool): ``True`` to enable, ``False`` to disable.
"""
# HACK: hide from jedi
if TYPE_CHECKING:
del Motor
del Number
del MaybeAwaitable
del Stop
+196 -4
View File
@@ -1,17 +1,18 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2022 The Pybricks Authors
# Copyright (c) 2018-2023 The Pybricks Authors
"""Common tools for timing and data logging."""
"""Common tools for timing, data logging, and linear algebra."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Optional, Sequence, Tuple, overload, Coroutine
if TYPE_CHECKING:
from ._common import MaybeAwaitable, MaybeAwaitableTuple
from .parameters import Number
def wait(time: Number) -> None:
def wait(time: Number) -> MaybeAwaitable:
"""wait(time)
Pauses the user program for a specified amount of time.
@@ -97,6 +98,197 @@ class DataLog:
"""
class Matrix:
"""Mathematical representation of a matrix. It supports
addition (``A + B``), subtraction (``A - B``),
and matrix multiplication (``A * B``) for matrices of compatible size.
It also supports scalar multiplication (``c * A`` or ``A * c``)
and scalar division (``A / c``).
A :class:`.Matrix` object is immutable."""
def __add__(self, other) -> Matrix:
...
def __iadd__(self, other) -> Matrix:
...
def __sub__(self, other) -> Matrix:
...
def __isub__(self, other) -> Matrix:
...
def __mul__(self, other) -> Matrix:
...
def __rmul__(self, other) -> Matrix:
...
def __imul__(self, other) -> Matrix:
...
def __truediv__(self, other) -> Matrix:
...
def __itruediv__(self, other) -> Matrix:
...
def __floordiv__(self, other) -> Matrix:
...
def __ifloordiv__(self, other) -> Matrix:
...
def __init__(self, rows: Sequence[Sequence[float]]):
"""Matrix(rows)
Arguments:
rows (list): List of rows. Each row is itself a list of numbers.
"""
@property
def T(self) -> Matrix: # noqa: N802
"""Returns a new :class:`.Matrix` that is the transpose of the
original."""
@property
def shape(self) -> Tuple[int, int]:
"""Returns a tuple (``m``, ``n``),
where ``m`` is the number of rows and ``n`` is the number of columns.
"""
@overload
def vector(x: float, y: float) -> Matrix:
"""
Convenience function to create a :class:`.Matrix` with the shape (``2``, ``1``).
Arguments:
x (float): x-coordinate of the vector.
y (float): y-coordinate of the vector.
Returns:
A matrix with the shape of a column vector.
"""
@overload
def vector(x: float, y: float, z: float) -> Matrix:
"""
Convenience function to create a :class:`.Matrix` with the shape (``3``, ``1``).
Arguments:
x (float): x-coordinate of the vector.
y (float): y-coordinate of the vector.
z (float): z-coordinate of the vector.
Returns:
A matrix with the shape of a column vector.
"""
def vector(*args):
"""
vector(x, y) -> Matrix
vector(x, y, z) -> Matrix
Convenience function to create a :class:`.Matrix` with the
shape (``2``, ``1``) or (``3``, ``1``).
Arguments:
x (float): x-coordinate of the vector.
y (float): y-coordinate of the vector.
z (float): z-coordinate of the vector (optional).
Returns:
A matrix with the shape of a column vector.
"""
def cross(a: Matrix, b: Matrix) -> Matrix:
"""
cross(a, b) -> Matrix
Gets the cross product ``a`` × ``b`` of two vectors.
Arguments:
a (Matrix): A three-dimensional vector.
b (Matrix): A three-dimensional vector.
Returns:
The cross product, also a three-dimensional vector.
"""
def read_input_byte() -> Optional[int]:
"""
read_input_byte() -> int | None
Reads one byte from standard input without blocking.
Returns:
The numeric value of the byte read or ``None`` if no data is available.
"""
def hub_menu(*symbols: int | str) -> int | str:
"""
hub_menu(symbol1, symbol2, ...) -> int | str
Shows a menu on the hub display and waits for the user to select an item
using the buttons. Can be used in your own menu-program that lets you
choose which of your other programs to run.
Note that this is just a convenience function that combines the display,
buttons, and waits to make a simple menu. This means that it can be used
anywhere in a program, not just at the start.
Arguments:
symbol1 (int or str): The first symbol to show in the menu.
symbol2 (int or str): The second symbol, and so on...
Returns:
The selected symbol.
"""
def multitask(*coroutines: Coroutine, race=False) -> MaybeAwaitableTuple:
"""
multitask(coroutine1, coroutine2, ...) -> Tuple
Runs multiple coroutines concurrently. This creates a new coroutine that
can be used like any other, including in another ``multitask`` statement.
Arguments:
coroutines (coroutine, coroutine, ...): One or more coroutines to run
in parallel.
race (bool): Choose ``False`` to wait for all coroutines to finish.
Choose ``True`` to wait for one coroutine to finish and then
cancel the others, as if it's a "race".
Returns:
Tuple of the return values of each coroutine. Unfinished coroutines
will have ``None`` as their return value.
"""
def run_task(coroutine: Coroutine):
"""
run_task(coroutine)
Runs a coroutine from start to finish while blocking the rest of the
program. This is used primarily to run the main coroutine of a program.
Arguments:
coroutine (coroutine): The main coroutine to run.
"""
# HACK: hide from jedi
if TYPE_CHECKING:
del Number
del MaybeAwaitable
del MaybeAwaitableTuple
+209 -3
View File
@@ -23,6 +23,7 @@ from typing import (
Any,
Callable,
Dict,
Hashable,
Iterable,
Iterator,
List,
@@ -33,6 +34,7 @@ from typing import (
SupportsFloat,
SupportsInt,
Tuple,
TypeVar,
Union,
overload,
)
@@ -55,6 +57,8 @@ _int = int
_str = str
_type = type
_Self = TypeVar("_Self")
# Functions and types
@@ -1074,8 +1078,9 @@ def round(*args):
truncate trailing zeros. To print numbers nicely, format strings instead::
# print two decimal places
print('my number: %.2f' % number) print('my number:
{:.2f}'.format(number))
print('my number: %.2f' % number)
print('my number: {:.2f}'.format(number))
print(f'my number: {number:.2f}')
Arguments:
number (float): The number to be rounded.
@@ -1083,6 +1088,207 @@ def round(*args):
"""
class set:
@overload
def __init__(self) -> None:
...
@overload
def __init__(self, iterable: Iterable[Hashable]) -> None:
...
def __init__(self, *args) -> None:
"""
set()
set(iterable)
Creates a new set.
With no arguments, creates a new empty set, otherwise creates a set
containing unique items of *iterable*.
Sets can also be created using a set literal::
my_set = {1, 2, 3}
Elements of a set must be hashable. There are only a few types, like
:class:`list` that aren't hashable.
Args:
iterable: An iterable of hashable objects.
"""
def copy(self: _Self) -> _Self:
"""
copy() -> set
Returns a shallow copy of the set.
Returns:
A new set.
"""
def difference(self: _Self, *others: set) -> _Self:
"""
difference(other1, other2, ...) -> set
Returns a new set with elements that are not in any of the other sets.
The difference can also be computed using the ``-`` operator::
diff = s - other
Args:
others: 1 or more other sets.
Returns:
A new set.
"""
def intersection(self: _Self, *others: set) -> _Self:
"""
intersection(other1, other2, ...) -> set
Returns a new set with elements that are common between this set and
all other sets.
The intersection can also be computed using the ``&`` operator::
intersect = s & other
Args:
others: 1 or more other sets.
Returns:
A new set.
"""
def isdisjoint(self, other: set) -> bool:
"""
isdisjoint(other) -> bool
Tests if a set and *other* have no elements in common.
Args:
other: Another set.
Returns:
``True`` if this set has no elements in common with *other*,
otherwise ``False``.
"""
def issubset(self, other: set) -> bool:
"""
issubset(other) -> bool
Tests if a set is a subset of *other*.
The test can also be performed using using the ``<=`` operator::
if s <= other:
# s is subset of other
...
Args:
other: Another set.
Returns:
``True`` if this set is a subset of *other*, otherwise ``False``.
"""
def issuperset(self, other: set) -> bool:
"""
issuperset(other) -> bool
Tests if a set is a superset of *other*.
The test can also be performed using using the ``>=`` operator::
if s >= other:
# s is superset of other
...
Args:
other: Another set.
Returns:
``True`` if this set is a superset of *other*, otherwise ``False``.
"""
def symmetric_difference(self: _Self, other: set) -> _Self:
"""
symmetric_difference(other) -> bool
Returns a new set with elements in one set or the other but not in both.
The symmetric difference can also be computed using the ``^`` operator::
diff = s ^ other
Args:
other: Another set.
Returns:
A new set.
"""
def union(self: _Self, *others: set) -> _Self:
"""
union(other1, other2, ...) -> set
Returns a new set with elements from this set and all other sets.
The union can also be computed using the ``|`` operator::
u = s | other
Args:
others: 1 or more other sets.
Returns:
A new set.
"""
def __contains__(self, item: Hashable) -> bool:
...
def __len__(self) -> int:
...
def __bool__(self) -> bool:
...
def __gt__(self, other: set) -> bool:
...
def __lt__(self, other: set) -> bool:
...
def __ge__(self, other: set) -> bool:
...
def __le__(self, other: set) -> bool:
...
def __eq__(self, other: set) -> bool:
...
def __ne__(self, other: set) -> bool:
...
def __sub__(self: _Self, other: set) -> _Self:
...
def __and__(self: _Self, other: set) -> _Self:
...
def __or__(self: _Self, other: set) -> _Self:
...
def __xor__(self: _Self, other: set) -> _Self:
...
def setattr(object: Any, name: _str, value: Any) -> None:
"""
setattr(object, name, value)
@@ -1169,7 +1375,7 @@ class str:
If no argument is given, this creates an empty ``str`` object.
Arguments:
object: If only this argument is given, this returns the stirng
object: If only this argument is given, this returns the string
representation of the object.
encoding (str): If the first argument is a ``bytearray`` or ``bytes``
object and the encoding argument is ``"utf-8"``, this will decode
+1 -1
View File
@@ -88,7 +88,7 @@ def getrandbits(k: int) -> int:
"""
getrandbits(k) -> int
Gets a random integer :math:`N` satisfying :math:`0 \\leq N < 2^{\\text{bits}}`.
Gets a random integer :math:`N` satisfying :math:`0 \\leq N < 2^{\\text{k}}`.
Arguments:
k (int): How many bits to use for the result.