mirror of
https://github.com/pybricks/pybricks-api.git
synced 2026-09-11 17:14:41 +00:00
initial EV3 bluetooth mailbox docs
This commit is contained in:
committed by
Laurens Valk
parent
f16876cca9
commit
7acbcbd874
+1
-1
@@ -14,6 +14,6 @@ install:
|
||||
|
||||
script:
|
||||
- pipenv run python setup.py build
|
||||
- make -C doc html SOURCEDIR=api
|
||||
- pipenv run make -C doc html SOURCEDIR=api
|
||||
- pipenv run flake8
|
||||
- pipenv run doc8
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
:mod:`bluetooth <pybricks.bluetooth>` -- Bluetooth
|
||||
==================================================
|
||||
|
||||
.. automodule:: pybricks.bluetooth
|
||||
:no-members:
|
||||
|
||||
.. currentmodule:: pybricks.bluetooth
|
||||
|
||||
|
||||
EV3 Mailboxes
|
||||
-------------
|
||||
|
||||
Pybricks MicroPython provides a mailbox implementation that is compatible with
|
||||
the standard LEGO firmware. This can be used to communicate between multiple
|
||||
EV3 bricks running Pybrick MicroPython, the standard LEGO firmware or a
|
||||
combination of the two.
|
||||
|
||||
.. note:: See :doc:`ev3_mailboxes` for a more general overview.
|
||||
|
||||
.. autoclass:: EV3MailboxClient
|
||||
|
||||
.. autoclass:: EV3MailboxServer
|
||||
|
||||
.. autoclass:: EV3MailboxMixIn
|
||||
|
||||
.. autodata:: ALL_BRICKS
|
||||
:annotation:
|
||||
@@ -0,0 +1,106 @@
|
||||
EV3 Mailboxes
|
||||
=============
|
||||
|
||||
TODO: introduction... messages are sent immediately, received messages are held
|
||||
in mailbox for getting later.
|
||||
|
||||
.. rubric:: Pairing
|
||||
|
||||
Before two EV3s can communicate with each other via Bluetooth, they must be
|
||||
paired.
|
||||
|
||||
TODO: screenshots of pairing with brickman, don't press the connect button!
|
||||
|
||||
|
||||
.. rubric:: Client and server
|
||||
|
||||
Programs can be written using either an EV3 mailbox *client* object or an EV3
|
||||
mailbox *server* object.
|
||||
|
||||
The only difference between the *client* and the *server* is which one
|
||||
initiates the connection at the beginning of the program. After that, sending
|
||||
and receiving messages is bidirectional and works the same from either point of
|
||||
view.
|
||||
|
||||
The *server* waits for an incoming connection while the *client* initiates the
|
||||
connection. Therefore, the server program must always be started first. If not,
|
||||
both programs will wait forever for a connection.
|
||||
|
||||
|
||||
Here is a basic example where two EV3s are connected and send greetings to
|
||||
each other.
|
||||
|
||||
.. rubric:: Client program
|
||||
|
||||
.. literalinclude::
|
||||
../../pybricks-projects/snippets/ev3/bluetooth_client/client.py
|
||||
|
||||
.. rubric:: Server program
|
||||
|
||||
.. literalinclude::
|
||||
../../pybricks-projects/snippets/ev3/bluetooth_server/server.py
|
||||
|
||||
|
||||
.. rubric:: EV3-G compatibility
|
||||
|
||||
TODO: screenshots of EV3-G programs, difference between client and server
|
||||
program is that client program has connect block. Server is always running.
|
||||
|
||||
TODO: show equivalent blocks
|
||||
|
||||
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.get_logic`
|
||||
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.get_numeric`
|
||||
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.get_text`
|
||||
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.send_logic`
|
||||
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.send_numeric`
|
||||
- :meth:`pybricks.bluetooth.EV3MailboxMixIn.send_text`
|
||||
|
||||
|
||||
.. rubric:: Sending objects as text
|
||||
|
||||
Simple Python objects, like dictionaries, can be encoded as text using the
|
||||
builtin ``repr`` function and turned in to objects again using the builtin
|
||||
``eval`` function.
|
||||
|
||||
Example::
|
||||
|
||||
# Server program
|
||||
my_obj = { 'a': 1, 'b': 2 }
|
||||
server.send_text(ALL_BRICKS, 'my_obj', repr(my_obj))
|
||||
|
||||
::
|
||||
|
||||
# Client program
|
||||
client.wait_for_update('my_obj')
|
||||
my_obj = eval(client.get_text('my_obj'))
|
||||
|
||||
|
||||
.. warning:: In general, ``eval`` is considered a security risk because it
|
||||
can execute arbitrary code! Never use ``eval`` with untrusted data, like
|
||||
like data received from the Internet.
|
||||
|
||||
|
||||
.. rubric:: More than two bricks
|
||||
|
||||
A single client EV3 can connect to multiple server EV3s or a single server EV3
|
||||
can accept connections from multiple clients.
|
||||
|
||||
TODO: the actual implementation needs to be updated to match this example.
|
||||
|
||||
Example::
|
||||
|
||||
client = EV3MailboxClient()
|
||||
|
||||
# connect to 4 different servers
|
||||
client.connect(SERVER1)
|
||||
client.connect(SERVER2)
|
||||
client.connect(SERVER3)
|
||||
client.connect(SERVER4)
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
server = EV3MailboxServer()
|
||||
|
||||
# wait for 4 clients to connect
|
||||
server.wait_for_connection(4)
|
||||
@@ -29,6 +29,7 @@ Intro
|
||||
tools
|
||||
robotics
|
||||
media
|
||||
bluetooth
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
@@ -37,4 +38,5 @@ Intro
|
||||
|
||||
signaltypes
|
||||
motors
|
||||
ev3_mailboxes
|
||||
.. frames
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.directives import flag
|
||||
from docutils.parsers.rst import Directive
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.domains.python import PyClassmember, PythonDomain
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
@@ -116,6 +120,7 @@ nitpick_ignore = [
|
||||
('py:class', 'object'),
|
||||
('py:class', 'str'),
|
||||
('py:class', 'tuple'),
|
||||
('py:exc', 'OSError'),
|
||||
('py:exc', 'RuntimeError'),
|
||||
('py:exc', 'TypeError'),
|
||||
('py:exc', 'ValueError'),
|
||||
@@ -268,6 +273,29 @@ texinfo_documents = [
|
||||
]
|
||||
|
||||
|
||||
# -- .. availability:: directive
|
||||
|
||||
class AvailabilityDirective(Directive):
|
||||
has_content = True
|
||||
option_spec = {
|
||||
'movehub': flag,
|
||||
'cityhub': flag,
|
||||
'cplushub': flag,
|
||||
'ev3dev-stretch': flag,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
if not self.options:
|
||||
raise self.error('Must specify at least one platform.')
|
||||
# TODO: make links to platform pages
|
||||
return [nodes.emphasis(text='Availability: '),
|
||||
nodes.Text(', '.join(self.options))]
|
||||
|
||||
|
||||
def setup(app: Sphinx):
|
||||
app.add_directive('availability', AvailabilityDirective)
|
||||
|
||||
|
||||
# -- Python domain hacks ---------------------------------------------------
|
||||
|
||||
real_get_signature_prefix = PyClassmember.get_signature_prefix
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (C) 2020 David Lechner
|
||||
|
||||
"""This module provides classes for working with Bluetooth Classic.
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
|
||||
from socketserver import BaseServer, ThreadingMixIn, StreamRequestHandler
|
||||
|
||||
try:
|
||||
from socket import BDADDR_ANY
|
||||
except ImportError:
|
||||
BDADDR_ANY = '00:00:00:00:00:00'
|
||||
|
||||
|
||||
__all__ = ['BDADDR_ANY', 'RFCOMMServer', 'ThreadingRFCOMMServer',
|
||||
'StreamRequestHandler', 'ALL_BRICKS', 'EV3MailboxServer',
|
||||
'EV3MailboxClient']
|
||||
|
||||
|
||||
class RFCOMMServer(BaseServer):
|
||||
"""Object that simplifies setting up an RFCOMM socket server.
|
||||
|
||||
This is based on the ``socketserver.SocketServer`` class in the Python
|
||||
standard library.
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
|
||||
|
||||
class ThreadingRFCOMMServer(ThreadingMixIn, RFCOMMServer):
|
||||
"""Version of :class:`RFCOMMServer` that handles connections in a new
|
||||
thread.
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: version
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
|
||||
|
||||
ALL_BRICKS = None
|
||||
"""Can be used in ``send`` methods to broadcast to call connected bricks.
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
|
||||
EV3_RFCOMM_CHANNEL = 1
|
||||
|
||||
|
||||
class EV3MailboxMixIn:
|
||||
"""Methods shared by both :class:`EV3MailboxServer` and
|
||||
:class:`EV3MailboxClient`
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
def get_raw_data(self, mbox):
|
||||
"""Gets the current raw data from a mailbox.
|
||||
|
||||
Arguments:
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
|
||||
Returns:
|
||||
bytes:
|
||||
The current mailbox raw data or ``None`` if nothing has ever
|
||||
been delivered to the mailbox.
|
||||
"""
|
||||
return b''
|
||||
|
||||
def get_packed_data(self, mbox, fmt):
|
||||
"""Gets the current packed data from a mailbox.
|
||||
|
||||
Arguments:
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
fmt (str):
|
||||
``ustruct.unpack()`` format string used to decode the binary
|
||||
data.
|
||||
|
||||
Returns:
|
||||
tuple:
|
||||
The result of ``ustruct.unpack()`` on the mailbox data.
|
||||
|
||||
Raises:
|
||||
TypeError:
|
||||
``fmt`` is not a string.
|
||||
RuntimeError:
|
||||
``mbox`` is empty.
|
||||
"""
|
||||
return ()
|
||||
|
||||
def get_logic(self, mbox):
|
||||
"""Gets the current value of the mailbox as a boolean value.
|
||||
|
||||
This is compatible with the "logic" mailbox type in EV3-G.
|
||||
|
||||
Arguments:
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
The current value or ``None`` if the mailbox is empty.
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_numeric(self, mbox):
|
||||
"""Gets the current value of the mailbox as a floating point value.
|
||||
|
||||
This is compatible with the "numeric" mailbox type in EV3-G.
|
||||
|
||||
Arguments:
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
|
||||
Returns:
|
||||
float:
|
||||
The current value or ``None`` if the mailbox is empty.
|
||||
"""
|
||||
return 0.0
|
||||
|
||||
def get_text(self, mbox):
|
||||
"""Gets the current value of the mailbox as a string value.
|
||||
|
||||
This is compatible with the "text" mailbox type in EV3-G.
|
||||
|
||||
Arguments:
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
|
||||
Returns:
|
||||
str:
|
||||
The current value or ``None`` if the mailbox is empty.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def send_raw_data(self, brick, mbox, payload):
|
||||
"""Sends a mailbox value using raw bytes data.
|
||||
|
||||
.. todo:: Currently the Bluetooth address must be used instead of the
|
||||
the brick name.
|
||||
|
||||
Arguments:
|
||||
brick (str):
|
||||
The name or Bluetooth address of the brick or
|
||||
:data:`ALL_BRICKS` to broadcast to all connected devices.
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
payload (bytes):
|
||||
A bytes-like object that will be sent to the mailbox.
|
||||
"""
|
||||
|
||||
def send_packed_data(self, brick, mbox, fmt, *args):
|
||||
"""Sends a mailbox value using packed values.
|
||||
|
||||
.. todo:: Currently the Bluetooth address must be used instead of the
|
||||
the brick name.
|
||||
|
||||
Arguments:
|
||||
brick (str):
|
||||
The name or Bluetooth address of the brick or
|
||||
:data:`ALL_BRICKS` to broadcast to all connected devices.
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
fmt (str):
|
||||
Format string compatible with ``ustruct.pack()``
|
||||
*:
|
||||
Arguments for ``ustruct.pack()``
|
||||
"""
|
||||
|
||||
def send_logic(self, brick, mbox, value):
|
||||
"""Sends a boolean mailbox value.
|
||||
|
||||
This is compatible with the "logic" mailbox type in EV3-G.
|
||||
|
||||
.. todo:: Currently the Bluetooth address must be used instead of the
|
||||
the brick name.
|
||||
|
||||
Arguments:
|
||||
brick (str):
|
||||
The name or Bluetooth address of the brick or
|
||||
:data:`ALL_BRICKS` to broadcast to all connected devices.
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
value (bool):
|
||||
The value that will be delivered to the mailbox.
|
||||
"""
|
||||
|
||||
def send_numeric(self, brick, mbox, value):
|
||||
"""Sends a float mailbox value.
|
||||
|
||||
This is compatible with the "numeric" mailbox type in EV3-G.
|
||||
|
||||
.. todo:: Currently the Bluetooth address must be used instead of the
|
||||
the brick name.
|
||||
|
||||
Arguments:
|
||||
brick (str):
|
||||
The name or Bluetooth address of the brick or
|
||||
:data:`ALL_BRICKS` to broadcast to all connected devices.
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
value (bool):
|
||||
The value that will be delivered to the mailbox.
|
||||
"""
|
||||
|
||||
def send_text(self, brick, mbox, value):
|
||||
"""Sends a string mailbox value.
|
||||
|
||||
This is compatible with the "text" mailbox type in EV3-G.
|
||||
|
||||
.. todo:: Currently the Bluetooth address must be used instead of the
|
||||
the brick name.
|
||||
|
||||
Arguments:
|
||||
brick (str):
|
||||
The name or Bluetooth address of the brick or
|
||||
:data:`ALL_BRICKS` to broadcast to all connected devices.
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
value (bool):
|
||||
The value that will be delivered to the mailbox.
|
||||
"""
|
||||
|
||||
def wait_for_update(self, mbox):
|
||||
"""Waits until ``mbox`` receives a value.
|
||||
|
||||
Arguments:
|
||||
mbox (str):
|
||||
The name of the mailbox.
|
||||
"""
|
||||
|
||||
|
||||
class EV3MailboxServer(EV3MailboxMixIn, ThreadingRFCOMMServer):
|
||||
"""Object that represents an incoming Bluetooth connection from another
|
||||
EV3.
|
||||
|
||||
The remote EV3 can either be running MicroPython or the standard EV3
|
||||
firmare.
|
||||
|
||||
See :class:`EV3MailboxMixIn` for additional methods.
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def wait_for_connection(self, count=1):
|
||||
"""Waits for a :class:`EV3MailboxClient` on a remote device to connect.
|
||||
|
||||
Arguments:
|
||||
count (int):
|
||||
The number of remote connections to wait for.
|
||||
|
||||
Raises:
|
||||
OSError:
|
||||
There was a problem establishing the connection.
|
||||
"""
|
||||
|
||||
|
||||
class EV3MailboxClient(EV3MailboxMixIn, ThreadingMixIn):
|
||||
"""Object that represents an outgoing Bluetooth connection to another
|
||||
EV3.
|
||||
|
||||
The remote EV3 can either be running MicroPython or the standard EV3
|
||||
firmare.
|
||||
|
||||
See :class:`EV3MailboxMixIn` for additional methods.
|
||||
|
||||
.. availability::
|
||||
:ev3dev-stretch:
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
"""
|
||||
def __init__(self, brick):
|
||||
"""
|
||||
.. todo:: Currently the Bluetooth address must be used instead of the
|
||||
the brick name.
|
||||
|
||||
Arguments:
|
||||
brick (str):
|
||||
The name or Bluetooth address of the remote EV3 to connect to.
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
||||
def connect(self):
|
||||
"""Connects to an :class:`EV3MailboxServer` on another device.
|
||||
|
||||
The remote device must be paired and waiting for a connection. See
|
||||
:meth:`EV3MailboxServer.wait_for_connection`.
|
||||
|
||||
Raises:
|
||||
OSError:
|
||||
There was a problem establishing the connection.
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
"""Closes the connection."""
|
||||
Reference in New Issue
Block a user