Compare commits

...
Author SHA1 Message Date
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
David Lechner 16a0621e03 @pybricks/ide-docs v2.7.0 2022-12-20 11:05:36 -06:00
52 changed files with 2250 additions and 455 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ name: Build Python package and docs
on:
push:
tags-ignore:
- "*"
- "@pybricks/*"
pull_request:
paths:
- doc/**
@@ -26,7 +26,7 @@ jobs:
python-version: [3.8, 3.9]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
+4 -4
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
@@ -26,7 +26,7 @@ jobs:
poetry run python -m pip install --upgrade pip
poetry run python -m pip install --upgrade setuptools
poetry install
- uses: actions/setup-node@v1
- 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:
+20 -1
View File
@@ -4,7 +4,26 @@
## Unreleased
## 3.2.0c2 - 2022-12-20
## 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
+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
+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.
+10
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,12 @@ 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 the battery
.. automethod:: pybricks.hubs::EssentialHub.battery.voltage
+10
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
+10
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,12 @@ 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 the battery
.. automethod:: pybricks.hubs::TechnicHub.battery.voltage
-1
View File
@@ -60,7 +60,6 @@ above to reveal this menu.
parameters/index
tools/index
robotics
geometry
signaltypes
.. toctree::
+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
-----------------------
+37 -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:
@@ -103,9 +102,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 +112,47 @@
The :meth:`done` and :meth:`stalled` methods have been moved.
.. pybricks-requirements:: gyro
.. class:: GyroDriveBase
This class works just like the :class:`DriveBase`, but it uses the hub's
built-in gyroscope to drive straight and turn more accurately.
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.
Examples
-------------------
Driving straight and turning in place
**********************************************
The following program shows the basics of driving and turning.
To use the built-in gyro, just replace the two occurences of
:class:`DriveBase` with :class:`GyroDriveBase`.
.. literalinclude::
../../examples/pup/robotics/drivebase_basics.py
+33
View File
@@ -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.
+25 -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,24 @@
.. automethod:: pybricks.tools.StopWatch.reset
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
+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!")
+15 -57
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,21 @@ 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.
"""
pass
Version of :class:`RFCOMMServer` that handles connections in a new thread.
"""
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 +100,7 @@ class RFCOMMClient:
class ThreadingRFCOMMClient(ThreadingMixIn, RFCOMMClient):
pass
"""
Version of :class:`RFCOMMClient` that handles connections in a new thread.
"""
daemon_threads = True
@@ -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
+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.
+2 -2
View File
@@ -14,10 +14,10 @@ drive_base = DriveBase(left_motor, right_motor, wheel_diameter=56, axle_track=11
# 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.
+9
View File
@@ -4,6 +4,15 @@
## Unreleased
## 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
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pybricks_jedi"
version = "1.6.0"
version = "1.7.0"
description = "Code completion for Pybricks."
authors = ["The Pybricks Authors <team@pybricks.com>"]
license = "MIT"
+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
@@ -79,7 +79,12 @@ def test_hub_dot_imu_dot():
"acceleration",
"angular_velocity",
"heading",
"orientation",
"ready",
"reset_heading",
"rotation",
"settings",
"stationary",
"tilt",
"up",
]
+34 -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,18 @@ 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", "GyroDriveBase"]
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",
"Matrix",
"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__",
]
+5
View File
@@ -97,7 +97,12 @@ def test_hub_dot_imu_dot():
"acceleration",
"angular_velocity",
"heading",
"orientation",
"ready",
"reset_heading",
"rotation",
"settings",
"stationary",
"tilt",
"up",
]
+5
View File
@@ -67,7 +67,12 @@ def test_hub_dot_imu_dot():
"acceleration",
"angular_velocity",
"heading",
"orientation",
"ready",
"reset_heading",
"rotation",
"settings",
"stationary",
"tilt",
"up",
]
+32 -5
View File
@@ -31,7 +31,7 @@ def _get_function_signature(module: str, function: str) -> SignatureHelp:
FUNCTION_PARAMS = [
pytest.param("pybricks.tools", "wait", [(["time: Number"], "None")]),
pytest.param(
"pybricks.geometry",
"pybricks.tools",
"vector",
[
(["x: float", "y: float"], "Matrix"),
@@ -104,6 +104,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 +152,7 @@ CONSTRUCTOR_PARAMS = [
],
),
pytest.param(
"pybricks.geometry",
"pybricks.tools",
"Matrix",
[["rows: Sequence[Sequence[float]]"]],
),
@@ -306,12 +307,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 +406,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",
@@ -481,12 +496,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 +542,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",
@@ -637,12 +664,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(
+10
View File
@@ -2,6 +2,16 @@
<!-- refer to https://keepachangelog.com/en/1.0.0/ for guidance -->
## 2.8.0 - 2023-04-21
### Changed
- Updated docs to v3.3.0b4.
## 2.7.0 - 2022-12-20
### Changed
- Updated docs to v3.2.0c2.
## 2.6.0 - 2022-12-09
### Changed
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@pybricks/ide-docs",
"version": "2.6.0",
"version": "2.8.0",
"description": "Special build of Pybricks API docs for embedding in an IDE.",
"repository": {
"type": "git",
+5
View File
@@ -4,6 +4,11 @@
## Unreleased
## 1.7.0 - 2022-12-28
### Changed
- Updated `pybricks_jedi` Python package to v1.7.0.
## 1.6.0 - 2022-12-09
### Changed
+2 -2
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.7.0",
"description": "Binary distribution of pybricks-jedi Python package and dependencies for use with Pyodide.",
"repository": {
"type": "git",
@@ -38,7 +38,7 @@ subprocess.check_call(
"pip",
"download",
"--only-binary=any",
"pybricks-jedi==1.6.0",
"pybricks-jedi==1.7.0",
],
cwd=BUILD_DIR,
)
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pybricks"
version = "3.2.0c2"
version = "3.3.0a4"
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>" ]
+189 -21
View File
@@ -8,8 +8,8 @@ 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 .parameters import Number
@@ -219,18 +219,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 +245,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 +304,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 +359,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 +387,16 @@ 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. A lower value
means more precise movement; a larger value means
smoother movement. If no value is given, a suitable profile for
this motor type will be selected automatically.
"""
def angle(self) -> int:
@@ -353,11 +408,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.
@@ -731,7 +794,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 +991,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 +1090,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 +1118,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."""
+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)
+1 -2
View File
@@ -4,9 +4,8 @@
"""LEGO® Programmable Hubs."""
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:
+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."""
+7 -2
View File
@@ -38,8 +38,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 +57,16 @@ 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. A lower value
means more precise movement; a larger value means
smoother movement. If no value is given, a suitable profile for
this motor type will be selected automatically.
"""
def reset_angle(self, angle: Optional[Number] = None) -> None:
+18 -9
View File
@@ -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.
@@ -135,25 +133,30 @@ 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(
@@ -219,6 +222,12 @@ class DriveBase:
"""
class GyroDriveBase(DriveBase):
"""A robotic vehicle with two powered wheels and an optional support
wheel or caster. It measures the heading using the hub's built-in gyroscope,
which can make turning and driving straight more accurate."""
# HACK: hide from jedi
if TYPE_CHECKING:
del Motor
+128 -3
View File
@@ -1,11 +1,11 @@
# 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, Sequence, Tuple, overload
if TYPE_CHECKING:
from .parameters import Number
@@ -97,6 +97,131 @@ 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.
"""
# HACK: hide from jedi
if TYPE_CHECKING:
del Number
+3 -2
View File
@@ -1074,8 +1074,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.
+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.