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.
This commit is contained in:
David Lechner
2023-05-16 14:46:53 -05:00
committed by GitHub
parent b4ff3af36d
commit 3274cef849
16 changed files with 413 additions and 12 deletions
+1
View File
@@ -325,6 +325,7 @@ def on_missing_reference(
# 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²",
+22
View File
@@ -19,6 +19,17 @@ City Hub
.. automethod:: pybricks.hubs::CityHub.light.animate
.. rubric:: Using connectionless Bluetooth messaging
``ble.broadcast()`` does not work on ``CityHub`` due to a bug in the
Bluetooth chip firmware.
.. 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 +81,17 @@ Creating light animations
.. literalinclude::
../../../examples/pup/hub_common/build/light_animate_cityhub.py
Bluetooth examples
------------------
Observing data from other hubs
******************************
.. literalinclude::
../../../examples/pup/hub_common/build/ble_observe_cityhub.py
Button and system examples
----------------------------------
+27
View File
@@ -47,6 +47,16 @@ Essential Hub
.. 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
@@ -136,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
----------------------------------
+27
View File
@@ -89,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
@@ -247,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
----------------------------------
+27
View File
@@ -43,6 +43,16 @@ Technic Hub
.. 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
@@ -128,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
----------------------------------
+24
View File
@@ -0,0 +1,24 @@
# 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.
hub.ble.broadcast(left_angle, right_angle)
# Broadcasts are only sent every 100 milliseconds, so there is no reason
# to call the broadcast() method more often than that.
wait(100)
+39
View File
@@ -0,0 +1,39 @@
# 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 = data[0]
right_angle = data[1]
# 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
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",
+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",
+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",
"buttons",
"charger",
"display",
+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",
+38 -4
View File
@@ -78,17 +78,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",
+77 -1
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."""
@@ -1285,3 +1285,79 @@ 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, *args: Union[None, bool, int, float, str, bytes]) -> None:
"""broadcast(data0, data1, ...)
Starts broadcasting the given data values.
Each value can be any of ``int``, ``float``, ``str`, ``bytes``,
``None``, ``True``, or ``False``. The data is broadcasted on the
*broadcast_channel* you selected when initializing the hub.
The total data size is quite limited (26 bytes). ``None``, ``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.
Params:
args: Zero or more values to be broadcast.
.. versionadded:: 3.3
"""
def observe(
self, channel: int
) -> Optional[Tuple[Union[None, bool, int, float, str, bytes], ...]]:
"""observe(channel) -> tuple | None
Retrieves the last observed data for a given channel.
Args:
channel (int): The channel to observe (0 to 255).
Returns:
A tuple of the received data or ``None`` if no recent data is
available.
.. tip:: Receiving data is more reliable when the hub is not connected
to a computer or other devices at the same time.
.. 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 is useful for detecting how near the broadcasting device is. A close
device may have a signal strength around -40 dBm while a far away device
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
"""
+99 -7
View File
@@ -1,7 +1,10 @@
# 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 .media.ev3dev import Image as _Image
@@ -38,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:
@@ -49,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:
@@ -61,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
@@ -75,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.
"""
@@ -89,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
@@ -103,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
@@ -127,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
@@ -141,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.
"""