pybricks.messaging: Add HubNetwork.

This commit is contained in:
Laurens Valk committed 2026-10-06 21:08:11 +02:00
1 parent 796c600d72
commit 3c24dbed48
4 files changed
+240 -1

No files matched your search

+4 -1
View File
@@ -31,6 +31,7 @@ HUB_FEATURES = {
"ble",
"ble-extra",
"pupdevices",
"hub-network",
}
| FEATURES_MEDIUM,
"inventorhub": {
@@ -42,6 +43,7 @@ HUB_FEATURES = {
"ble",
"ble-extra",
"pupdevices",
"hub-network",
}
| FEATURES_MEDIUM,
"essentialhub": {
@@ -51,9 +53,10 @@ HUB_FEATURES = {
"ble",
"ble-extra",
"pupdevices",
"hub-network",
}
| FEATURES_MEDIUM,
"ev3brick": {"ev3devices", "nxtdevices", "image"} | FEATURES_MEDIUM,
"ev3brick": {"ev3devices", "nxtdevices", "image", "hub-network"} | FEATURES_MEDIUM,
}
+18
View File
@@ -60,3 +60,21 @@ Observing data from other hubs
.. literalinclude::
../../../examples/pup/ble_radio/ble_observe.py
Messaging between EV3 Bricks
----------------------------
.. pybricks-requirements:: hub-network
.. autoclass:: pybricks.messaging.HubNetwork
:no-members:
.. automethod:: pybricks.messaging.HubNetwork.address
.. automethod:: pybricks.messaging.HubNetwork.connect
.. automethod:: pybricks.messaging.HubNetwork.is_connected
.. automethod:: pybricks.messaging.HubNetwork.send
.. automethod:: pybricks.messaging.HubNetwork.inbox
+19
View File
@@ -20,6 +20,7 @@ def test_from_pybricks_messaging_import():
"BluetoothMailboxClient",
"BluetoothMailboxServer",
"Connection",
"HubNetwork",
"LogicMailbox",
"Mailbox",
"NumericMailbox",
@@ -44,6 +45,24 @@ def test_ble_radio_dot():
]
def test_hub_network_dot():
code = "\n".join(
[
"from pybricks.messaging import HubNetwork",
"network = HubNetwork()",
"network.",
]
)
completions: list[CompletionItem] = json.loads(complete(code, 3, 9))
assert [c["insertText"] for c in completions] == [
"address",
"connect",
"inbox",
"is_connected",
"send",
]
def test_app_data_dot():
code = "\n".join(
[
+199
View File
@@ -146,6 +146,205 @@ class BLERadio:
"""
class HubNetwork:
"""
Send and receive messages between supported hubs using Bluetooth.
Create this object in every program that takes part in the network. One
hub, called the *manager*, then connects to others identified by their
address. For example: :meth:`connect('00:16:53:12:34:56') <connect>`.
After that, all hubs are equal: each one can send messages to any other
brick.
You can find the address of each hub in the EV3 settings menu, or print it
with the :meth:`address` method. Connections are preserved when you
restart a program.
The protocol ensures that messages get delivered. But they will only be
seen in your program if there is enough room. The manager takes care of
relaying messages in the background, which only works while the manager
program is still running.
.. versionadded:: 4.1
"""
def __init__(self, inbox_size: int = 1024):
"""HubNetwork(inbox_size=1024)
Arguments:
inbox_size (int): How many bytes of messages to store for each
brick that sends them.
Raises:
RuntimeError: If a ``HubNetwork`` object already exists, or if
this is used after multitasking has started.
OSError: If this brick has no working Bluetooth.
ValueError: If ``inbox_size`` is too small to hold one message.
"""
def address(self) -> str:
"""address() -> str
Gets the Bluetooth address of this brick.
This is the same address that is shown on the brick's screen. You can
print it to find out which address to type in the programs of the
other bricks.
Returns:
The address of this brick, such as ``'00:16:53:AB:CD:EF'``. The
letters are always uppercase.
"""
return ""
def connect(self, address: str) -> MaybeAwaitable:
"""connect(address)
Connects to one other hub (EV3 Brick) to set up the network.
Only one hub in the network does this. That hub is called the manager.
The other hubs don't have to run a program yet. They just won't
receive messages until their programs run.
Connecting is slow. It takes up to about fifteen seconds per brick,
and it does not always work on the first try, so it tries again a few
times before it gives up. This is why you normally do this at the
start of your program.
A brick that is already connected is ready right away, so it is safe
to use this again later. This makes the second run very quick to start.
Arguments:
address (str): The Bluetooth address of the brick to connect to,
such as ``'00:16:53:12:34:56'``. You can connect to up to
seven other bricks.
Raises:
OSError: If the brick could not be reached. ValueError: If
``address`` is not a valid Bluetooth address, or if it is the
address of this brick.
"""
def is_connected(self, address: str | None = None) -> bool:
"""
is_connected() -> bool
is_connected(address) -> bool
Checks whether a brick has joined the network.
This is useful on the bricks that wait. They can keep checking
``is_connected()`` until the manager brick has reached them, instead
of guessing how long the network takes to set up.
Only the manager brick can use the ``address`` argument to check
individual bricks.
Arguments:
address (str): The Bluetooth address of the hub to check.
Returns:
``True`` if the hub has joined the network, ``False`` if not. If
no address is given, it is ``True`` if any hub is connected.
Raises:
ValueError: If ``address`` is not a valid Bluetooth address.
"""
return False
def send(
self,
data: bool
| float
| str
| bytes
| None
| tuple[bool | int | float | str | bytes | None, ...],
address: str | None = None,
) -> MaybeAwaitable:
"""
send(data) send(data, address)
Sends a message to all hubs on the network, or to one specific hub.
A message is one object, or a tuple of objects. An object may be of
type ``int``, ``float``, ``str``, ``bytes``, ``True``, ``False``, or
``None``. The brick that receives it gets the same objects back, so
sending ``(60, "left")`` arrives as ``(60, "left")``.
A message that is only ``bytes`` is sent just as it is, which is what
you want if you build your own messages. Such a message can hold 255
bytes. Every other message also carries a short description of the
objects in it, so a little less fits: ``True``, ``False`` and ``None``
take 2 bytes each, ``int`` and ``float`` take 5 bytes each, and
``str`` and ``bytes`` take the number of bytes in them plus 2 to 4.
One message holds up to 32 objects.
Messages from this brick to one other brick arrive in the order that
you sent them.
Arguments:
data: The message to send.
address (str): The Bluetooth address of
the brick to send to. Leave out to send to every other brick
on the network. A brick never receives its own messages.
Raises:
ValueError: If ``address`` is not a valid Bluetooth address, or if
the message is too big to send.
TypeError: If the message holds an object that cannot be sent.
"""
def inbox(
self, latest: bool = False
) -> tuple[
tuple[
str,
bool
| int
| float
| str
| bytes
| None
| tuple[bool | int | float | str | bytes | None, ...],
],
...,
]:
"""inbox(latest=False) -> tuple
Gets the messages that have arrived since you last read them.
This never waits. If nothing has arrived, you get an empty result, so
a ``for`` loop over it simply does nothing. Reading removes the messages from this brick's memory, so each message
is given to you only once.
Every brick that sends to you gets an inbox of its own internally.
When one brick sends more than fits in its inbox, its oldest message
is dropped. A brick that sends all the time cannot push another
brick's messages out.
A program never receives messages that were sent before it started.
Messages are given to you in the order that they arrived, no matter
which brick sent them.
Arguments:
latest (bool): Choose ``True`` to keep only the newest message
from each brick and throw the older ones away. This is what
you want when a brick keeps sending you its latest value, such
as a sensor reading, and only the newest one is of any use.
Returns:
A tuple of ``(address, data)`` pairs, where ``address`` is the
Bluetooth address of the brick that sent the message, and ``data``
is the message in the same form as it was sent. With ``latest``,
there is at most one pair per brick.
Raises:
ValueError: If a message does not match the description it carries
with it, which means that it was damaged on the way.
"""
return ()
class Connection:
@abstractmethod
def read_from_mailbox(self, name: str) -> bytes: ...